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