diff --git a/crates/collab/src/tests/integration_tests.rs b/crates/collab/src/tests/integration_tests.rs index 7756f31efa..7d6199b109 100644 --- a/crates/collab/src/tests/integration_tests.rs +++ b/crates/collab/src/tests/integration_tests.rs @@ -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(); diff --git a/crates/collab_ui/src/collab_titlebar_item.rs b/crates/collab_ui/src/collab_titlebar_item.rs index 7a9c2cd97e..95fe26fa11 100644 --- a/crates/collab_ui/src/collab_titlebar_item.rs +++ b/crates/collab_ui/src/collab_titlebar_item.rs @@ -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, - user_store: &ModelHandle, + user_store: ModelHandle, cx: &mut ViewContext, ) -> 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) { 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) { - 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::::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::( 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::::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::( peer_id.as_u64() as usize, diff --git a/crates/collab_ui/src/collab_ui.rs b/crates/collab_ui/src/collab_ui.rs index 557f9b58d9..3f3998fb6d 100644 --- a/crates/collab_ui/src/collab_ui.rs +++ b/crates/collab_ui/src/collab_ui.rs @@ -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, cx: &mut AppContext) { +pub fn init(app_state: &Arc, 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, 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::()) - .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); -} diff --git a/crates/collab_ui/src/contact_list.rs b/crates/collab_ui/src/contact_list.rs index 8cbba6030d..319df337d7 100644 --- a/crates/collab_ui/src/contact_list.rs +++ b/crates/collab_ui/src/contact_list.rs @@ -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>, } #[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)] @@ -161,6 +148,7 @@ pub struct ContactList { match_candidates: Vec, list_state: ListState, project: ModelHandle, + workspace: WeakViewHandle, user_store: ModelHandle, filter_editor: ViewHandle, collapsed_sections: Vec
, @@ -169,11 +157,7 @@ pub struct ContactList { } impl ContactList { - pub fn new( - project: ModelHandle, - user_store: ModelHandle, - cx: &mut ViewContext, - ) -> Self { + pub fn new(workspace: &ViewHandle, cx: &mut ViewContext) -> 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) { - let section = action.0; + fn toggle_expanded(&mut self, section: Section, cx: &mut ViewContext) { 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, ) -> AnyElement { + 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, ) -> AnyElement { + 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) { - 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>, + cx: &mut ViewContext, + ) { ActiveCall::global(cx) .update(cx, |call, cx| { call.invite(recipient_user_id, initial_project, cx) diff --git a/crates/collab_ui/src/contact_notification.rs b/crates/collab_ui/src/contact_notification.rs index 9ad8a22485..a998be8efd 100644 --- a/crates/collab_ui/src/contact_notification.rs +++ b/crates/collab_ui/src/contact_notification.rs @@ -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, user: Arc, @@ -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) { + fn dismiss(&mut self, cx: &mut ViewContext) { 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, - ) { + fn respond_to_contact_request(&mut self, accept: bool, cx: &mut ViewContext) { 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(); } diff --git a/crates/collab_ui/src/contacts_popover.rs b/crates/collab_ui/src/contacts_popover.rs index cbef4bfa88..60f0bf0e73 100644 --- a/crates/collab_ui/src/contacts_popover.rs +++ b/crates/collab_ui/src/contacts_popover.rs @@ -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, user_store: ModelHandle, + workspace: WeakViewHandle, _subscription: Option, } impl ContactsPopover { - pub fn new( - project: ModelHandle, - user_store: ModelHandle, - cx: &mut ViewContext, - ) -> Self { + pub fn new(workspace: &ViewHandle, cx: &mut ViewContext) -> 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) { - 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(); + } } } diff --git a/crates/collab_ui/src/incoming_call_notification.rs b/crates/collab_ui/src/incoming_call_notification.rs index d0cf893a0f..2e3048a4f8 100644 --- a/crates/collab_ui/src/incoming_call_notification.rs +++ b/crates/collab_ui/src/incoming_call_notification.rs @@ -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, 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, } impl IncomingCallNotification { - pub fn new(call: IncomingCall) -> Self { - Self { call } + pub fn new(call: IncomingCall, app_state: Weak) -> Self { + Self { call, app_state } } - fn respond_to_call(&mut self, action: &RespondToCall, cx: &mut ViewContext) { + fn respond(&mut self, accept: bool, cx: &mut ViewContext) { 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), ) diff --git a/crates/collab_ui/src/notifications.rs b/crates/collab_ui/src/notifications.rs index 66afea34c1..1dec5a8411 100644 --- a/crates/collab_ui/src/notifications.rs +++ b/crates/collab_ui/src/notifications.rs @@ -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( +pub fn render_user_notification( user: Arc, title: &'static str, body: Option<&'static str>, - dismiss_action: A, - buttons: Vec<(&'static str, Box)>, + on_dismiss: F, + buttons: Vec<(&'static str, Box)>)>, cx: &mut ViewContext, -) -> AnyElement { +) -> AnyElement +where + F: 'static + Fn(&mut V, &mut ViewContext), + V: View, +{ let theme = cx.global::().theme.clone(); let theme = &theme.contact_notification; @@ -64,9 +68,7 @@ pub fn render_user_notification( }) .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( Some( Flex::row() .with_children(buttons.into_iter().enumerate().map( - |(ix, (message, action))| { + |(ix, (message, handler))| { MouseEventHandler::::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( .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() diff --git a/crates/collab_ui/src/project_shared_notification.rs b/crates/collab_ui/src/project_shared_notification.rs index 4cdb18d97c..1304688ca1 100644 --- a/crates/collab_ui/src/project_shared_notification.rs +++ b/crates/collab_ui/src/project_shared_notification.rs @@ -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, 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, owner: Arc, + app_state: Weak, } impl ProjectSharedNotification { - fn new(owner: Arc, project_id: u64, worktree_root_names: Vec) -> Self { + fn new( + owner: Arc, + project_id: u64, + worktree_root_names: Vec, + app_state: Weak, + ) -> Self { Self { project_id, worktree_root_names, owner, + app_state, } } - fn join(&mut self, _: &JoinProject, cx: &mut ViewContext) { + fn join(&mut self, cx: &mut ViewContext) { 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) { + fn dismiss(&mut self, cx: &mut ViewContext) { 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::::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), ) diff --git a/crates/context_menu/src/context_menu.rs b/crates/context_menu/src/context_menu.rs index 01a04cfd22..6f66d710cb 100644 --- a/crates/context_menu/src/context_menu.rs +++ b/crates/context_menu/src/context_menu.rs @@ -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, - }, - ViewAction { - action: Box, - for_view: usize, - }, +impl From> for ContextMenuItemLabel { + fn from(s: Cow<'static, str>) -> Self { + Self::String(s) + } } -impl ContextMenuAction { - fn id(&self) -> TypeId { +impl From<&'static str> for ContextMenuItemLabel { + fn from(s: &'static str) -> Self { + Self::String(s.into()) + } +} + +impl From for ContextMenuItemLabel { + fn from(s: String) -> Self { + Self::String(s.into()) + } +} + +impl From for ContextMenuItemLabel +where + T: 'static + Fn(&mut MouseState, &theme::ContextMenuItem) -> AnyElement, +{ + fn from(f: T) -> Self { + Self::Element(Box::new(f)) + } +} + +pub enum ContextMenuItemAction { + Action(Box), + Handler(Arc)>), +} + +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, 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>, 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>, - view_id: usize, - action: impl 'static + Action, + pub fn handler( + label: impl Into, + handler: impl 'static + Fn(&mut ViewContext), ) -> 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 { 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.clicked = true; - } - fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext) { 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::::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() diff --git a/crates/copilot/src/copilot.rs b/crates/copilot/src/copilot.rs index a5d26f8254..6bc3622ab1 100644 --- a/crates/copilot/src/copilot.rs +++ b/crates/copilot/src/copilot.rs @@ -458,7 +458,7 @@ impl Copilot { } } - fn sign_in(&mut self, cx: &mut ModelContext) -> Task> { + pub fn sign_in(&mut self, cx: &mut ModelContext) -> Task> { if let CopilotServer::Running(server) = &mut self.server { let task = match &server.sign_in_status { SignInStatus::Authorized { .. } | SignInStatus::Unauthorized { .. } => { diff --git a/crates/copilot/src/sign_in.rs b/crates/copilot/src/sign_in.rs index d7aadb6262..fdb4828cd0 100644 --- a/crates/copilot/src/sign_in.rs +++ b/crates/copilot/src/sign_in.rs @@ -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; } }, )) diff --git a/crates/copilot_button/src/copilot_button.rs b/crates/copilot_button/src/copilot_button.rs index c8bfcc7550..a597bb7e47 100644 --- a/crates/copilot_button/src/copilot_button.rs +++ b/crates/copilot_button/src/copilot_button.rs @@ -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, -} - -#[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::() - .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::().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, 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.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::( @@ -264,15 +153,15 @@ impl CopilotButton { } } - pub fn deploy_copilot_start_menu( - &mut self, - _: &DeployCopilotStartMenu, - cx: &mut ViewContext, - ) { + pub fn deploy_copilot_start_menu(&mut self, cx: &mut ViewContext) { 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) { + pub fn deploy_copilot_menu(&mut self, cx: &mut ViewContext) { let settings = cx.global::(); 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::().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::().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, cx: &mut AppContext) { + let show_copilot_suggestions = cx + .global::() + .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::() 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); + } + } +} diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index 53ac43be2e..6973d83f9b 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -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.editor + .update(cx, |editor, cx| editor.added_to_workspace(workspace, cx)); + } + fn navigate(&mut self, data: Box, cx: &mut ViewContext) -> bool { self.editor .update(cx, |editor, cx| editor.navigate(data, cx)) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 02af04f569..eea418b211 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -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::(cx); @@ -509,7 +493,7 @@ pub struct Editor { pending_rename: Option, searchable: bool, cursor_shape: CursorShape, - workspace_id: Option, + workspace: Option<(WeakViewHandle, i64)>, keymap_context_layers: BTreeMap, 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) { + fn select(&mut self, phase: SelectPhase, cx: &mut ViewContext) { 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) { - 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, + ) { + 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| { - 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(); } diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index e71ab8ae4b..af4bc1674b 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -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::::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)); diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 1179109903..11d1ce8cae 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -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, ) -> 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, ) -> 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::::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::( id.into(), diff --git a/crates/editor/src/hover_popover.rs b/crates/editor/src/hover_popover.rs index 623b8ba13f..2932fa547e 100644 --- a/crates/editor/src/hover_popover.rs +++ b/crates/editor/src/hover_popover.rs @@ -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, -} - -#[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) { /// 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) { +pub fn hover_at(editor: &mut Editor, point: Option, cx: &mut ViewContext) { if cx.global::().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) -> bool { +pub fn hide_hover(editor: &mut Editor, cx: &mut ViewContext) -> 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::(|_, _| 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; diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 2eaf7b568e..83e971358d 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -794,7 +794,7 @@ impl Item for Editor { fn added_to_workspace(&mut self, workspace: &mut Workspace, cx: &mut ViewContext) { 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, @@ -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); } } }) diff --git a/crates/editor/src/link_go_to_definition.rs b/crates/editor/src/link_go_to_definition.rs index ee0367103e..69c45b9da8 100644 --- a/crates/editor/src/link_go_to_definition.rs +++ b/crates/editor/src/link_go_to_definition.rs @@ -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, - 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, @@ -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, + cmd_held: bool, + shift_held: bool, cx: &mut ViewContext, ) { let pending_nonempty_selection = editor.has_pending_nonempty_selection(); @@ -286,7 +251,7 @@ pub fn hide_link_definition(editor: &mut Editor, cx: &mut ViewContext) { pub fn go_to_fetched_definition( workspace: &mut Workspace, - &GoToFetchedDefinition { point }: &GoToFetchedDefinition, + point: DisplayPoint, cx: &mut ViewContext, ) { 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, ) { 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::(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::(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()); diff --git a/crates/editor/src/mouse_context_menu.rs b/crates/editor/src/mouse_context_menu.rs index a7a37dd5a6..b892fffbca 100644 --- a/crates/editor/src/mouse_context_menu.rs +++ b/crates/editor/src/mouse_context_menu.rs @@ -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, ) { 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() { diff --git a/crates/editor/src/scroll.rs b/crates/editor/src/scroll.rs index ab8bbe0d5e..21894dea88 100644 --- a/crates/editor/src/scroll.rs +++ b/crates/editor/src/scroll.rs @@ -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) -> Vector2F { @@ -323,13 +319,14 @@ impl Editor { } pub fn set_scroll_anchor(&mut self, scroll_anchor: ScrollAnchor, cx: &mut ViewContext) { - 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, ) { - 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) { diff --git a/crates/editor/src/scroll/actions.rs b/crates/editor/src/scroll/actions.rs index 3b73a4bcbd..a79b0f2449 100644 --- a/crates/editor/src/scroll/actions.rs +++ b/crates/editor/src/scroll/actions.rs @@ -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, -} - -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.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, + cx: &mut ViewContext, + ) { + 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) { diff --git a/crates/gpui/src/app/action.rs b/crates/gpui/src/app/action.rs index 0b035f12c3..5b4df68a65 100644 --- a/crates/gpui/src/app/action.rs +++ b/crates/gpui/src/app/action.rs @@ -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> { - Err($crate::anyhow::anyhow!("internal action")) - } - } - )* - }; -} - #[doc(hidden)] #[macro_export] macro_rules! __impl_action { diff --git a/crates/gpui/src/presenter/event_context.rs b/crates/gpui/src/presenter/event_context.rs deleted file mode 100644 index d4258a6d1e..0000000000 --- a/crates/gpui/src/presenter/event_context.rs +++ /dev/null @@ -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, - 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, - pub(crate) handled: bool, - pub(crate) invalidated_views: HashSet, -} - -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(&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 { - 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) { - self.app - .dispatch_any_action_at(self.window_id, *self.view_stack.last().unwrap(), action) - } - - pub fn dispatch_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 - } -} diff --git a/crates/gpui/src/presenter/event_dispatcher.rs b/crates/gpui/src/presenter/event_dispatcher.rs deleted file mode 100644 index 960c565bd4..0000000000 --- a/crates/gpui/src/presenter/event_dispatcher.rs +++ /dev/null @@ -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, - - last_mouse_moved_event: Option, - cursor_regions: Vec, - mouse_regions: Vec<(MouseRegion, usize)>, - clicked_regions: Vec, - clicked_button: Option, - mouse_position: Vector2F, - hovered_region_ids: HashSet, -} - -impl EventDispatcher { - pub fn new(window_id: usize, font_cache: Arc) -> 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, MouseButton)> { - self.clicked_button.map(|button| { - ( - self.clicked_regions - .iter() - .filter_map(MouseRegion::id) - .collect(), - button, - ) - }) - } - - pub fn hovered_region_ids(&self) -> HashSet { - 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 = 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 { - 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 { - 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 - } -} diff --git a/crates/menu/src/menu.rs b/crates/menu/src/menu.rs index 227e6c6c28..81716028b9 100644 --- a/crates/menu/src/menu.rs +++ b/crates/menu/src/menu.rs @@ -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]); diff --git a/crates/picker/src/picker.rs b/crates/picker/src/picker.rs index 83717af0fd..62ffd417fe 100644 --- a/crates/picker/src/picker.rs +++ b/crates/picker/src/picker.rs @@ -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 View for Picker { // 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 Picker { 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 Picker { cx.notify(); } - pub fn select_index(&mut self, action: &SelectIndex, cx: &mut ViewContext) { - let index = action.0; + pub fn select_index(&mut self, index: usize, cx: &mut ViewContext) { if self.delegate.match_count() > 0 { self.confirmed = true; self.delegate.set_selected_index(index, cx); diff --git a/crates/project_panel/src/project_panel.rs b/crates/project_panel/src/project_panel.rs index c144ba569e..0ca187bfe5 100644 --- a/crates/project_panel/src/project_panel.rs +++ b/crates/project_panel/src/project_panel.rs @@ -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) { + fn deploy_context_menu( + &mut self, + position: Vector2F, + entry_id: ProjectEntryId, + cx: &mut ViewContext, + ) { 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) { - let entry_id = action.0; + fn toggle_expanded(&mut self, entry_id: ProjectEntryId, cx: &mut ViewContext) { 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) { + fn open_entry( + &mut self, + entry_id: ProjectEntryId, + focus_opened_item: bool, + cx: &mut ViewContext, + ) { 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, ) { 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::>() .currently_dragged::(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; } } diff --git a/crates/recent_projects/src/recent_projects.rs b/crates/recent_projects/src/recent_projects.rs index 70acfcd5d5..6429448f75 100644 --- a/crates/recent_projects/src/recent_projects.rs +++ b/crates/recent_projects/src/recent_projects.rs @@ -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) { + cx.add_async_action( + move |_: &mut Workspace, _: &OpenRecent, cx: &mut ViewContext| { + toggle(app_state.clone(), cx) + }, + ); RecentProjects::init(cx); } -fn toggle( - _: &mut Workspace, - _: &OpenRecent, - cx: &mut ViewContext, -) -> Option>> { +fn toggle(app_state: Weak, cx: &mut ViewContext) -> Option>> { 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; struct RecentProjectsDelegate { + workspace: WeakViewHandle, workspace_locations: Vec, + app_state: Weak, selected_match_index: usize, matches: Vec, } impl RecentProjectsDelegate { - fn new(workspace_locations: Vec) -> Self { + fn new( + workspace: WeakViewHandle, + workspace_locations: Vec, + app_state: Weak, + ) -> 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) { - 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); } } diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 986a4b4bfb..ac478a8a2c 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -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.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.results_editor.update(cx, |editor, _| { editor.set_nav_history(Some(nav_history)); diff --git a/crates/terminal_view/src/terminal_button.rs b/crates/terminal_view/src/terminal_button.rs index e1c8edc0c7..6349cbbfa4 100644 --- a/crates/terminal_view/src/terminal_button.rs +++ b/crates/terminal_view/src/terminal_button.rs @@ -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, -} - -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, popup_menu: ViewHandle, @@ -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, - ) { - let mut menu_options = vec![ContextMenuItem::item("New Terminal", NewTerminal)]; + pub fn deploy_terminal_menu(&mut self, cx: &mut ViewContext) { + 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::(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) { - if let Some(workspace) = self.workspace.upgrade(cx) { - workspace.update(cx, |workspace, cx| { - let terminal = workspace - .items_of_type::(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 { diff --git a/crates/terminal_view/src/terminal_element.rs b/crates/terminal_view/src/terminal_element.rs index 55e10a3195..392b050f43 100644 --- a/crates/terminal_view/src/terminal_element.rs +++ b/crates/terminal_view/src/terminal_element.rs @@ -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) { diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 3c7d9a0ae7..5e04fc9825 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -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) { + pub fn deploy_context_menu(&mut self, position: Vector2F, cx: &mut ViewContext) { 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(); diff --git a/crates/theme/src/ui.rs b/crates/theme/src/ui.rs index 0f58ee08ad..1198e81e92 100644 --- a/crates/theme/src/ui.rs +++ b/crates/theme/src/ui.rs @@ -139,27 +139,11 @@ pub fn keystroke_label( ) -> Container { // 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( - view_id: usize, - label_text: &'static str, - label_style: &ContainedText, - keystroke_style: &ContainedText, - action: Box, -) -> Container { 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(), diff --git a/crates/workspace/src/dock.rs b/crates/workspace/src/dock.rs index 67cfea3d9a..33cd833019 100644 --- a/crates/workspace/src/dock.rs +++ b/crates/workspace/src/dock.rs @@ -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| { - Dock::move_dock(workspace, dock_anchor, true, cx); - }, - ); cx.add_action( |workspace: &mut Workspace, _: &AnchorDockRight, cx: &mut ViewContext| { 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, ) -> Self { let position = DockPosition::Hidden(cx.global::().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) { diff --git a/crates/workspace/src/dock/toggle_dock_button.rs b/crates/workspace/src/dock/toggle_dock_button.rs index babd08298a..bf85183938 100644 --- a/crates/workspace/src/dock/toggle_dock_button.rs +++ b/crates/workspace/src/dock/toggle_dock_button.rs @@ -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() { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index b30107c726..38e6b92b34 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -365,7 +365,7 @@ impl ItemHandle for ViewHandle { 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 ItemHandle for ViewHandle { 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, diff --git a/crates/workspace/src/notifications.rs b/crates/workspace/src/notifications.rs index 05f8e98acd..57749a5c2b 100644 --- a/crates/workspace/src/notifications.rs +++ b/crates/workspace/src/notifications.rs @@ -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.dismiss_notification::(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.dismiss_notification::(id, cx); + } + fn dismiss_notification_internal( &mut self, type_id: TypeId, diff --git a/crates/workspace/src/pane.rs b/crates/workspace/src/pane.rs index aa913986de..41f4d5d111 100644 --- a/crates/workspace/src/pane.rs +++ b/crates/workspace/src/pane.rs @@ -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, - pub to: WeakViewHandle, - pub destination_index: usize, -} - #[derive(Clone, Deserialize, PartialEq)] pub struct GoBack { #[serde(skip_deserializing)] @@ -94,36 +87,7 @@ pub struct GoForward { pub pane: Option>, } -#[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, -} - 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, docked: Option, _background_actions: BackgroundActions, - _workspace_id: usize, + workspace: WeakViewHandle, } pub struct ItemNavHistory { @@ -315,7 +221,7 @@ impl TabBarContextMenu { impl Pane { pub fn new( - workspace_id: usize, + workspace: WeakViewHandle, docked: Option, background_actions: BackgroundActions, cx: &mut ViewContext, @@ -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) { + fn deploy_split_menu(&mut self, cx: &mut ViewContext) { 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) { + fn deploy_dock_menu(&mut self, cx: &mut ViewContext) { 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) { + fn deploy_new_menu(&mut self, cx: &mut ViewContext) { 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, ) { - 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( +fn render_tab_bar_button)>( index: usize, icon: &'static str, cx: &mut ViewContext, - action: A, + on_click: F, context_menu: Option>, ) -> AnyElement { enum TabBarButton {} @@ -1887,9 +1840,7 @@ fn render_tab_bar_button( .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()), diff --git a/crates/workspace/src/pane/dragged_item_receiver.rs b/crates/workspace/src/pane/dragged_item_receiver.rs index ae6b250fb6..961205b9ee 100644 --- a/crates/workspace/src/pane/dragged_item_receiver.rs +++ b/crates/workspace/src/pane/dragged_item_receiver.rs @@ -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( event: MouseUp, + workspace: WeakViewHandle, pane: &WeakViewHandle, index: usize, allow_same_pane: bool, @@ -126,36 +133,74 @@ pub fn handle_dropped_item( { 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); + } + }); + } + }); + } } } } diff --git a/crates/workspace/src/pane_group.rs b/crates/workspace/src/pane_group.rs index 49dd8db606..55032b4bc1 100644 --- a/crates/workspace/src/pane_group.rs +++ b/crates/workspace/src/pane_group.rs @@ -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>, active_pane: &ViewHandle, + app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { 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>, active_pane: &ViewHandle, + app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { 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::::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>, active_pane: &ViewHandle, + app_state: &Arc, cx: &mut ViewContext, ) -> AnyElement { let last_member_ix = self.members.len() - 1; @@ -370,8 +383,15 @@ impl PaneAxis { flex = cx.global::().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; diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 7524b84bf8..1a622babb3 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -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, - split_direction: SplitDirection, - from: WeakViewHandle, - item_id_to_move: usize, -} - -#[derive(Clone, PartialEq)] -pub struct SplitWithProjectEntry { - pane_to_split: WeakViewHandle, - split_direction: SplitDirection, - project_entry: ProjectEntryId, -} - -#[derive(Clone, PartialEq)] -pub struct OpenProjectEntryInPane { - pane: WeakViewHandle, - 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, cx: &mut AppContext) { @@ -255,81 +193,53 @@ pub fn init(app_state: Arc, 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| { - 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.add_async_action({ - let app_state = Arc::downgrade(&app_state); - move |workspace, action: &OpenPaths, cx: &mut ViewContext| { - 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)) - }; - - 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(()); + 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) + }); } - Some(window_id) - } else { - None - }; - cx.update(|cx| open_paths(&action.paths, &app_state, window_id_to_replace, cx)) - .await?; - Ok(()) - })) + }) + .detach(); + } } }); + cx.add_action({ + let app_state = Arc::downgrade(&app_state); + move |_, _: &Open, cx: &mut ViewContext| { + let mut paths = cx.prompt_for_paths(PathPromptOptions { + files: true, + directories: true, + multiple: true, + }); + 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(); + } + } + }) + .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, 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| { let pane = workspace.active_pane().clone(); @@ -384,30 +291,6 @@ pub fn init(app_state: Arc, 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, cx: &mut AppContext) { .detach(); }); - cx.add_action(|workspace: &mut Workspace, alert: &Toast, cx| { - workspace.dismiss_notification::(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::(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, - client: Arc, - user_store: ModelHandle, remote_entity_subscription: Option, - fs: Arc, modal: Option, center: PaneGroup, left_sidebar: ViewHandle, @@ -641,7 +503,7 @@ pub struct Workspace { active_call: Option<(ModelHandle, Vec)>, leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>, database_id: WorkspaceId, - background_actions: BackgroundActions, + app_state: Arc, _window_subscriptions: [Subscription; 3], _apply_leader_updates: Task>, _observe_current_user: Task>, @@ -671,8 +533,7 @@ impl Workspace { serialized_workspace: Option, workspace_id: WorkspaceId, project: ModelHandle, - dock_default_factory: DockDefaultItemFactory, - background_actions: BackgroundActions, + app_state: Arc, cx: &mut ViewContext, ) -> 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 { + &self.app_state + } + pub fn user_store(&self) -> &ModelHandle { - &self.user_store + &self.app_state.user_store } pub fn project(&self) -> &ModelHandle { @@ -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) { @@ -1243,6 +1100,37 @@ impl Workspace { }) } + pub fn open_workspace_for_paths( + &mut self, + paths: Vec, + app_state: Arc, + cx: &mut ViewContext, + ) -> Task> { + 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, ) -> Task, 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.project - .update(cx, |project, cx| project.remove_worktree(*worktree_id, cx)); - } - fn project_path_for_path( project: ModelHandle, abs_path: &Path, @@ -1561,8 +1440,14 @@ impl Workspace { } fn add_pane(&mut self, cx: &mut ViewContext) -> ViewHandle { - 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) { - 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) { + 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) { - 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, + split_direction: SplitDirection, + from: WeakViewHandle, + item_id_to_move: usize, + cx: &mut ViewContext, + ) { + 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, + split_direction: SplitDirection, + project_entry: ProjectEntryId, cx: &mut ViewContext, ) -> Option>> { - 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, cx: &mut ViewContext) { 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, ) -> Option>> { - 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 { 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, cx: &mut ViewContext) -> 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, + cx: &mut AppContext, +) -> Task> { + 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::()) + .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 { 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)]) diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 081938788f..679fd39e2f 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -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, 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 { diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index 4325835cdb..494739a967 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -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, 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, 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);