Detailed changes
@@ -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()
- )
- .with_style(style.container)
- .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,
+ };
- if worktree.is_shared {
- worktree_row = worktree_row
- .with_cursor_style(CursorStyle::PointingHand)
- .on_click(move |cx| {
+ 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(),
+ )
+ .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()
}