mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-12 21:32:40 +00:00
Allow clicking on worktrees to share, unshare, join, and leave
Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
41a1514cec
commit
c24d439eb1
9 changed files with 200 additions and 94 deletions
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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()
|
||||
}),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue