Allow clicking on worktrees to share, unshare, join, and leave

Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
Max Brunsfeld 2021-09-21 16:27:57 -07:00
parent 41a1514cec
commit c24d439eb1
9 changed files with 200 additions and 94 deletions

View file

@ -134,21 +134,18 @@ guest_avatar_spacing = 8
extends = "$text.1"
padding = { left = 5 }
[people_panel.own_worktree]
extends = "$people_panel.unshared_worktree"
color = "$syntax.variant"
[people_panel.joined_worktree]
extends = "$people_panel.own_worktree"
[people_panel.hovered_unshared_worktree]
extends = "$people_panel.shared_worktree"
background = "$state.hover"
corner_radius = 6
[people_panel.shared_worktree]
extends = "$people_panel.unshared_worktree"
color = "$text.0.color"
[people_panel.hovered_shared_worktree]
extends = "$people_panel.shared_worktree"
background = "$state.hover"
corner_radius = 6
extends = "$people_panel.hovered_unshared_worktree"
color = "$text.0.color"
[people_panel.tree_branch]
width = 1

View file

@ -2320,6 +2320,7 @@ impl Editor {
buffer::Event::Saved => cx.emit(Event::Saved),
buffer::Event::FileHandleChanged => cx.emit(Event::FileHandleChanged),
buffer::Event::Reloaded => cx.emit(Event::FileHandleChanged),
buffer::Event::Closed => cx.emit(Event::Closed),
buffer::Event::Reparsed => {}
}
}
@ -2449,6 +2450,7 @@ pub enum Event {
Dirtied,
Saved,
FileHandleChanged,
Closed,
}
impl Entity for Editor {
@ -2556,6 +2558,10 @@ impl workspace::ItemView for Editor {
matches!(event, Event::Activate)
}
fn should_close_item_on_event(event: &Self::Event) -> bool {
matches!(event, Event::Closed)
}
fn should_update_tab_on_event(event: &Self::Event) -> bool {
matches!(
event,

View file

@ -801,6 +801,10 @@ impl Buffer {
cx.emit(Event::FileHandleChanged);
}
pub fn close(&mut self, cx: &mut ModelContext<Self>) {
cx.emit(Event::Closed);
}
pub fn language(&self) -> Option<&Arc<Language>> {
self.language.as_ref()
}
@ -2264,6 +2268,7 @@ pub enum Event {
FileHandleChanged,
Reloaded,
Reparsed,
Closed,
}
impl Entity for Buffer {

View file

@ -16,16 +16,6 @@ pub fn menus(state: &Arc<AppState>) -> Vec<Menu<'static>> {
action: Box::new(super::About),
},
MenuItem::Separator,
MenuItem::Action {
name: "Sign In",
keystroke: None,
action: Box::new(super::Authenticate),
},
MenuItem::Action {
name: "Share",
keystroke: None,
action: Box::new(workspace::ShareWorktree),
},
MenuItem::Action {
name: "Quit",
keystroke: Some("cmd-q"),

View file

@ -14,6 +14,9 @@ use gpui::{
use postage::watch;
action!(JoinWorktree, u64);
action!(LeaveWorktree, u64);
action!(ShareWorktree, u64);
action!(UnshareWorktree, u64);
pub struct PeoplePanel {
collaborators: ListState,
@ -106,7 +109,7 @@ impl PeoplePanel {
.left()
.constrained()
.with_height(host_avatar_height)
.boxed()
.boxed(),
)
.boxed(),
)
@ -161,70 +164,78 @@ impl PeoplePanel {
.boxed(),
)
.with_child({
let mut worktree_row =
MouseEventHandler::new::<PeoplePanel, _, _, _>(
worktree_id as usize,
cx,
|mouse_state, _| {
let style =
if Some(collaborator.user.id) == current_user_id {
&theme.own_worktree
} else if worktree.is_shared {
if worktree.guests.iter().any(|guest| {
Some(guest.id) == current_user_id
}) {
&theme.joined_worktree
} else if mouse_state.hovered {
&theme.hovered_shared_worktree
} else {
&theme.shared_worktree
}
} else {
&theme.unshared_worktree
};
let is_host = Some(collaborator.user.id) == current_user_id;
let is_guest = !is_host
&& worktree
.guests
.iter()
.any(|guest| Some(guest.id) == current_user_id);
let is_shared = worktree.is_shared;
Container::new(
Flex::row()
.with_child(
Label::new(
worktree.root_name.clone(),
style.text.clone(),
)
.aligned()
.left()
.constrained()
.with_height(guest_avatar_height)
.boxed()
)
.with_children(worktree.guests.iter().filter_map(
|participant| {
participant.avatar.clone().map(|avatar| {
Image::new(avatar)
.with_style(theme.guest_avatar)
.contained()
.with_margin_left(
theme.guest_avatar_spacing,
)
.boxed()
})
},
))
.boxed()
MouseEventHandler::new::<PeoplePanel, _, _, _>(
worktree_id as usize,
cx,
|mouse_state, _| {
let style = match (worktree.is_shared, mouse_state.hovered)
{
(false, false) => &theme.unshared_worktree,
(false, true) => &theme.hovered_unshared_worktree,
(true, false) => &theme.shared_worktree,
(true, true) => &theme.hovered_shared_worktree,
};
Container::new(
Flex::row()
.with_child(
Label::new(
worktree.root_name.clone(),
style.text.clone(),
)
.aligned()
.left()
.constrained()
.with_height(guest_avatar_height)
.boxed(),
)
.with_style(style.container)
.boxed()
},
);
if worktree.is_shared {
worktree_row = worktree_row
.with_cursor_style(CursorStyle::PointingHand)
.on_click(move |cx| {
.with_children(worktree.guests.iter().filter_map(
|participant| {
participant.avatar.clone().map(|avatar| {
Image::new(avatar)
.with_style(theme.guest_avatar)
.contained()
.with_margin_left(
theme.guest_avatar_spacing,
)
.boxed()
})
},
))
.boxed(),
)
.with_style(style.container)
.boxed()
},
)
.with_cursor_style(if is_host || is_shared {
CursorStyle::PointingHand
} else {
CursorStyle::Arrow
})
.on_click(move |cx| {
if is_shared {
if is_host {
cx.dispatch_action(UnshareWorktree(worktree_id));
} else if is_guest {
cx.dispatch_action(LeaveWorktree(worktree_id));
} else {
cx.dispatch_action(JoinWorktree(worktree_id))
});
}
worktree_row.expanded(1.0).boxed()
}
} else if is_host {
cx.dispatch_action(ShareWorktree(worktree_id));
}
})
.expanded(1.0)
.boxed()
})
.boxed()
}),

View file

@ -111,11 +111,10 @@ pub struct PeoplePanel {
pub container: ContainerStyle,
pub host_avatar: ImageStyle,
pub host_username: ContainedText,
pub own_worktree: ContainedText,
pub joined_worktree: ContainedText,
pub shared_worktree: ContainedText,
pub hovered_shared_worktree: ContainedText,
pub unshared_worktree: ContainedText,
pub hovered_unshared_worktree: ContainedText,
pub guest_avatar: ImageStyle,
pub guest_avatar_spacing: f32,
pub tree_branch: TreeBranch,

View file

@ -128,6 +128,7 @@ impl UserStore {
}
this.update(&mut cx, |this, cx| {
collaborators.sort_by(|a, b| a.user.github_login.cmp(&b.user.github_login));
this.collaborators = collaborators.into();
cx.notify();
});

View file

@ -7,7 +7,7 @@ use crate::{
editor::Buffer,
fs::Fs,
language::LanguageRegistry,
people_panel::{JoinWorktree, PeoplePanel},
people_panel::{JoinWorktree, LeaveWorktree, PeoplePanel, ShareWorktree, UnshareWorktree},
project_browser::ProjectBrowser,
rpc,
settings::Settings,
@ -43,7 +43,6 @@ use std::{
action!(Open, Arc<AppState>);
action!(OpenPaths, OpenParams);
action!(OpenNew, Arc<AppState>);
action!(ShareWorktree);
action!(Save);
action!(DebugElements);
@ -56,9 +55,11 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_action(Workspace::save_active_item);
cx.add_action(Workspace::debug_elements);
cx.add_action(Workspace::open_new_file);
cx.add_action(Workspace::share_worktree);
cx.add_action(Workspace::toggle_sidebar_item);
cx.add_action(Workspace::share_worktree);
cx.add_action(Workspace::unshare_worktree);
cx.add_action(Workspace::join_worktree);
cx.add_action(Workspace::leave_worktree);
cx.add_bindings(vec![
Binding::new("cmd-s", Save, None),
Binding::new("cmd-alt-i", DebugElements, None),
@ -175,6 +176,9 @@ pub trait ItemView: View {
fn should_activate_item_on_event(_: &Self::Event) -> bool {
false
}
fn should_close_item_on_event(_: &Self::Event) -> bool {
false
}
fn should_update_tab_on_event(_: &Self::Event) -> bool {
false
}
@ -273,6 +277,10 @@ impl<T: ItemView> ItemViewHandle for ViewHandle<T> {
fn set_parent_pane(&self, pane: &ViewHandle<Pane>, cx: &mut MutableAppContext) {
pane.update(cx, |_, cx| {
cx.subscribe(self, |pane, item, event, cx| {
if T::should_close_item_on_event(event) {
pane.close_item(item.id(), cx);
return;
}
if T::should_activate_item_on_event(event) {
if let Some(ix) = pane.item_index(&item) {
pane.activate_item(ix, cx);
@ -814,21 +822,33 @@ impl Workspace {
};
}
fn share_worktree(&mut self, _: &ShareWorktree, cx: &mut ViewContext<Self>) {
fn share_worktree(&mut self, action: &ShareWorktree, cx: &mut ViewContext<Self>) {
let rpc = self.rpc.clone();
let remote_id = action.0;
cx.spawn(|this, mut cx| {
async move {
rpc.authenticate_and_connect(&cx).await?;
let share_task = this.update(&mut cx, |this, cx| {
let worktree = this.worktrees.iter().next()?;
worktree.update(cx, |worktree, cx| {
let worktree = worktree.as_local_mut()?;
Some(worktree.share(cx))
})
let task = this.update(&mut cx, |this, cx| {
for worktree in &this.worktrees {
let task = worktree.update(cx, |worktree, cx| {
worktree.as_local_mut().and_then(|worktree| {
if worktree.remote_id() == Some(remote_id) {
Some(worktree.share(cx))
} else {
None
}
})
});
if task.is_some() {
return task;
}
}
None
});
if let Some(share_task) = share_task {
if let Some(share_task) = task {
share_task.await?;
}
@ -839,6 +859,23 @@ impl Workspace {
.detach();
}
fn unshare_worktree(&mut self, action: &UnshareWorktree, cx: &mut ViewContext<Self>) {
let remote_id = action.0;
for worktree in &self.worktrees {
if worktree.update(cx, |worktree, cx| {
if let Some(worktree) = worktree.as_local_mut() {
if worktree.remote_id() == Some(remote_id) {
worktree.unshare(cx);
return true;
}
}
false
}) {
break;
}
}
}
fn join_worktree(&mut self, action: &JoinWorktree, cx: &mut ViewContext<Self>) {
let rpc = self.rpc.clone();
let languages = self.languages.clone();
@ -862,6 +899,31 @@ impl Workspace {
.detach();
}
fn leave_worktree(&mut self, action: &LeaveWorktree, cx: &mut ViewContext<Self>) {
let remote_id = action.0;
cx.spawn(|this, mut cx| {
async move {
this.update(&mut cx, |this, cx| {
this.worktrees.retain(|worktree| {
worktree.update(cx, |worktree, cx| {
if let Some(worktree) = worktree.as_remote_mut() {
if worktree.remote_id() == remote_id {
worktree.close_all_buffers(cx);
return false;
}
}
true
})
})
});
Ok(())
}
.log_err()
})
.detach();
}
fn add_pane(&mut self, cx: &mut ViewContext<Self>) -> ViewHandle<Pane> {
let pane = cx.add_view(|_| Pane::new(self.settings.clone()));
let pane_id = pane.id();

View file

@ -943,6 +943,10 @@ impl LocalWorktree {
}
}
pub fn remote_id(&self) -> Option<u64> {
*self.remote_id.borrow()
}
pub fn next_remote_id(&self) -> impl Future<Output = Option<u64>> {
let mut remote_id = self.remote_id.clone();
async move {
@ -1095,6 +1099,23 @@ impl LocalWorktree {
})
}
pub fn unshare(&mut self, cx: &mut ModelContext<Worktree>) {
self.share.take();
let rpc = self.rpc.clone();
let remote_id = self.remote_id();
cx.foreground()
.spawn(
async move {
if let Some(worktree_id) = remote_id {
rpc.send(proto::UnshareWorktree { worktree_id }).await?;
}
Ok(())
}
.log_err(),
)
.detach()
}
fn share_request(&self, cx: &mut ModelContext<Worktree>) -> Task<Option<proto::ShareWorktree>> {
let remote_id = self.next_remote_id();
let snapshot = self.snapshot();
@ -1229,6 +1250,20 @@ impl RemoteWorktree {
})
}
pub fn remote_id(&self) -> u64 {
self.remote_id
}
pub fn close_all_buffers(&mut self, cx: &mut MutableAppContext) {
for (_, buffer) in self.open_buffers.drain() {
if let RemoteBuffer::Loaded(buffer) = buffer {
if let Some(buffer) = buffer.upgrade(cx) {
buffer.update(cx, |buffer, cx| buffer.close(cx))
}
}
}
}
fn snapshot(&self) -> Snapshot {
self.snapshot.clone()
}