From c24d439eb1dc1d8dc18ffae47bfaf3a0c352c49a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 21 Sep 2021 16:27:57 -0700 Subject: [PATCH] Allow clicking on worktrees to share, unshare, join, and leave Co-Authored-By: Nathan Sobo --- zed/assets/themes/_base.toml | 15 ++-- zed/src/editor.rs | 6 ++ zed/src/editor/buffer.rs | 5 ++ zed/src/menus.rs | 10 --- zed/src/people_panel.rs | 135 +++++++++++++++++++---------------- zed/src/theme.rs | 3 +- zed/src/user.rs | 1 + zed/src/workspace.rs | 84 +++++++++++++++++++--- zed/src/worktree.rs | 35 +++++++++ 9 files changed, 200 insertions(+), 94 deletions(-) diff --git a/zed/assets/themes/_base.toml b/zed/assets/themes/_base.toml index c30893de44..4ee79d60d4 100644 --- a/zed/assets/themes/_base.toml +++ b/zed/assets/themes/_base.toml @@ -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 diff --git a/zed/src/editor.rs b/zed/src/editor.rs index 25403d3aae..a06b7ee013 100644 --- a/zed/src/editor.rs +++ b/zed/src/editor.rs @@ -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, diff --git a/zed/src/editor/buffer.rs b/zed/src/editor/buffer.rs index 4c093032dd..8186b422ae 100644 --- a/zed/src/editor/buffer.rs +++ b/zed/src/editor/buffer.rs @@ -801,6 +801,10 @@ impl Buffer { cx.emit(Event::FileHandleChanged); } + pub fn close(&mut self, cx: &mut ModelContext) { + cx.emit(Event::Closed); + } + pub fn language(&self) -> Option<&Arc> { self.language.as_ref() } @@ -2264,6 +2268,7 @@ pub enum Event { FileHandleChanged, Reloaded, Reparsed, + Closed, } impl Entity for Buffer { diff --git a/zed/src/menus.rs b/zed/src/menus.rs index 1fe4bd922a..e885d31209 100644 --- a/zed/src/menus.rs +++ b/zed/src/menus.rs @@ -16,16 +16,6 @@ pub fn menus(state: &Arc) -> Vec> { 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"), diff --git a/zed/src/people_panel.rs b/zed/src/people_panel.rs index 6c770b703a..78a0ec153f 100644 --- a/zed/src/people_panel.rs +++ b/zed/src/people_panel.rs @@ -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::( - 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::( + 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() }), diff --git a/zed/src/theme.rs b/zed/src/theme.rs index 112b4265ae..568bb29f5a 100644 --- a/zed/src/theme.rs +++ b/zed/src/theme.rs @@ -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, diff --git a/zed/src/user.rs b/zed/src/user.rs index 4c59f64b83..7467b16f9b 100644 --- a/zed/src/user.rs +++ b/zed/src/user.rs @@ -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(); }); diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index 89a48a99fd..4581e38d90 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -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); action!(OpenPaths, OpenParams); action!(OpenNew, Arc); -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 ItemViewHandle for ViewHandle { fn set_parent_pane(&self, pane: &ViewHandle, 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) { + fn share_worktree(&mut self, action: &ShareWorktree, cx: &mut ViewContext) { 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) { + 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) { 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) { + 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) -> ViewHandle { let pane = cx.add_view(|_| Pane::new(self.settings.clone())); let pane_id = pane.id(); diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index 521eabb65e..803304e6f3 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -943,6 +943,10 @@ impl LocalWorktree { } } + pub fn remote_id(&self) -> Option { + *self.remote_id.borrow() + } + pub fn next_remote_id(&self) -> impl Future> { let mut remote_id = self.remote_id.clone(); async move { @@ -1095,6 +1099,23 @@ impl LocalWorktree { }) } + pub fn unshare(&mut self, cx: &mut ModelContext) { + 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) -> Task> { 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() }