Set up logic for starting following

Co-Authored-By: Antonio Scandurra <me@as-cii.com>
This commit is contained in:
Max Brunsfeld 2022-03-17 10:46:54 -07:00
parent 2b4738d82d
commit 9716ff7964
4 changed files with 142 additions and 28 deletions

View file

@ -83,7 +83,8 @@ message Envelope {
Follow follow = 72; Follow follow = 72;
FollowResponse follow_response = 73; FollowResponse follow_response = 73;
UpdateFollower update_follower = 74; UpdateFollowers update_followers = 74;
Unfollow unfollow = 75;
} }
} }
@ -537,16 +538,27 @@ message UpdateDiagnostics {
repeated Diagnostic diagnostics = 3; repeated Diagnostic diagnostics = 3;
} }
message Follow {} message Follow {
uint64 project_id = 1;
uint32 leader_id = 2;
}
message FollowResponse { message FollowResponse {
uint64 current_view_id = 1; uint64 current_view_id = 1;
repeated View views = 2; repeated View views = 2;
} }
message UpdateFollower { message UpdateFollowers {
uint64 current_view_id = 1; uint64 project_id = 1;
repeated ViewUpdate view_updates = 2; uint64 current_view_id = 2;
repeated View created_views = 3;
repeated ViewUpdate updated_views = 4;
repeated uint32 follower_ids = 5;
}
message Unfollow {
uint64 project_id = 1;
uint32 leader_id = 2;
} }
// Entities // Entities

View file

@ -147,6 +147,8 @@ messages!(
(BufferSaved, Foreground), (BufferSaved, Foreground),
(ChannelMessageSent, Foreground), (ChannelMessageSent, Foreground),
(Error, Foreground), (Error, Foreground),
(Follow, Foreground),
(FollowResponse, Foreground),
(FormatBuffers, Foreground), (FormatBuffers, Foreground),
(FormatBuffersResponse, Foreground), (FormatBuffersResponse, Foreground),
(GetChannelMessages, Foreground), (GetChannelMessages, Foreground),
@ -196,6 +198,7 @@ messages!(
(SendChannelMessageResponse, Foreground), (SendChannelMessageResponse, Foreground),
(ShareProject, Foreground), (ShareProject, Foreground),
(Test, Foreground), (Test, Foreground),
(Unfollow, Foreground),
(UnregisterProject, Foreground), (UnregisterProject, Foreground),
(UnregisterWorktree, Foreground), (UnregisterWorktree, Foreground),
(UnshareProject, Foreground), (UnshareProject, Foreground),
@ -203,6 +206,7 @@ messages!(
(UpdateBufferFile, Foreground), (UpdateBufferFile, Foreground),
(UpdateContacts, Foreground), (UpdateContacts, Foreground),
(UpdateDiagnosticSummary, Foreground), (UpdateDiagnosticSummary, Foreground),
(UpdateFollowers, Foreground),
(UpdateWorktree, Foreground), (UpdateWorktree, Foreground),
); );
@ -212,6 +216,7 @@ request_messages!(
ApplyCompletionAdditionalEdits, ApplyCompletionAdditionalEdits,
ApplyCompletionAdditionalEditsResponse ApplyCompletionAdditionalEditsResponse
), ),
(Follow, FollowResponse),
(FormatBuffers, FormatBuffersResponse), (FormatBuffers, FormatBuffersResponse),
(GetChannelMessages, GetChannelMessagesResponse), (GetChannelMessages, GetChannelMessagesResponse),
(GetChannels, GetChannelsResponse), (GetChannels, GetChannelsResponse),
@ -248,6 +253,7 @@ entity_messages!(
ApplyCompletionAdditionalEdits, ApplyCompletionAdditionalEdits,
BufferReloaded, BufferReloaded,
BufferSaved, BufferSaved,
Follow,
FormatBuffers, FormatBuffers,
GetCodeActions, GetCodeActions,
GetCompletions, GetCompletions,
@ -266,11 +272,13 @@ entity_messages!(
SaveBuffer, SaveBuffer,
SearchProject, SearchProject,
StartLanguageServer, StartLanguageServer,
Unfollow,
UnregisterWorktree, UnregisterWorktree,
UnshareProject, UnshareProject,
UpdateBuffer, UpdateBuffer,
UpdateBufferFile, UpdateBufferFile,
UpdateDiagnosticSummary, UpdateDiagnosticSummary,
UpdateFollowers,
UpdateLanguageServer, UpdateLanguageServer,
RegisterWorktree, RegisterWorktree,
UpdateWorktree, UpdateWorktree,

View file

@ -1,5 +1,7 @@
use super::{ItemHandle, SplitDirection}; use super::{ItemHandle, SplitDirection};
use crate::{Item, Settings, WeakItemHandle, Workspace}; use crate::{Item, Settings, WeakItemHandle, Workspace};
use anyhow::{anyhow, Result};
use client::PeerId;
use collections::{HashMap, VecDeque}; use collections::{HashMap, VecDeque};
use gpui::{ use gpui::{
action, action,
@ -105,6 +107,13 @@ pub struct Pane {
active_toolbar_visible: bool, active_toolbar_visible: bool,
} }
pub(crate) struct FollowerState {
pub(crate) leader_id: PeerId,
pub(crate) current_view_id: usize,
pub(crate) items_by_leader_view_id:
HashMap<usize, (Option<ProjectEntryId>, Box<dyn ItemHandle>)>,
}
pub trait Toolbar: View { pub trait Toolbar: View {
fn active_item_changed( fn active_item_changed(
&mut self, &mut self,
@ -313,6 +322,21 @@ impl Pane {
cx.notify(); cx.notify();
} }
pub(crate) fn set_follow_state(
&mut self,
follower_state: FollowerState,
cx: &mut ViewContext<Self>,
) -> Result<()> {
let current_view_id = follower_state.current_view_id as usize;
let (project_entry_id, item) = follower_state
.items_by_leader_view_id
.get(&current_view_id)
.ok_or_else(|| anyhow!("invalid current view id"))?
.clone();
self.add_item(project_entry_id, item, cx);
Ok(())
}
pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> { pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> {
self.items.iter().map(|(_, view)| view) self.items.iter().map(|(_, view)| view)
} }

View file

@ -7,7 +7,7 @@ pub mod sidebar;
mod status_bar; mod status_bar;
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use client::{Authenticate, ChannelList, Client, User, UserStore}; use client::{proto, Authenticate, ChannelList, Client, PeerId, User, UserStore};
use clock::ReplicaId; use clock::ReplicaId;
use collections::HashMap; use collections::HashMap;
use gpui::{ use gpui::{
@ -42,16 +42,18 @@ use std::{
}; };
use theme::{Theme, ThemeRegistry}; use theme::{Theme, ThemeRegistry};
type ItemBuilders = HashMap< type ProjectItemBuilders = HashMap<
TypeId, TypeId,
Arc< fn(usize, ModelHandle<Project>, AnyModelHandle, &mut MutableAppContext) -> Box<dyn ItemHandle>,
dyn Fn( >;
usize,
ModelHandle<Project>, type FollowedItemBuilders = Vec<
AnyModelHandle, fn(
&mut MutableAppContext, ViewHandle<Pane>,
) -> Box<dyn ItemHandle>, ModelHandle<Project>,
>, &mut Option<proto::view::Variant>,
&mut MutableAppContext,
) -> Option<Task<Result<(Option<ProjectEntryId>, Box<dyn ItemHandle>)>>>,
>; >;
action!(Open, Arc<AppState>); action!(Open, Arc<AppState>);
@ -108,18 +110,18 @@ pub fn init(cx: &mut MutableAppContext) {
]); ]);
} }
pub fn register_project_item<V>(cx: &mut MutableAppContext) pub fn register_project_item<I: ProjectItem>(cx: &mut MutableAppContext) {
where cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
V: ProjectItem, builders.insert(TypeId::of::<I::Item>(), |window_id, project, model, cx| {
{ let item = model.downcast::<I::Item>().unwrap();
cx.update_default_global(|builders: &mut ItemBuilders, _| { Box::new(cx.add_view(window_id, |cx| I::for_project_item(project, item, cx)))
builders.insert( });
TypeId::of::<V::Item>(), });
Arc::new(move |window_id, project, model, cx| { }
let item = model.downcast::<V::Item>().unwrap();
Box::new(cx.add_view(window_id, |cx| V::for_project_item(project, item, cx))) pub fn register_followed_item<I: FollowedItem>(cx: &mut MutableAppContext) {
}), cx.update_default_global(|builders: &mut FollowedItemBuilders, _| {
); builders.push(I::for_state_message)
}); });
} }
@ -214,6 +216,17 @@ pub trait ProjectItem: Item {
) -> Self; ) -> Self;
} }
pub trait FollowedItem: Item {
type UpdateMessage;
fn for_state_message(
pane: ViewHandle<Pane>,
project: ModelHandle<Project>,
state: &mut Option<proto::view::Variant>,
cx: &mut MutableAppContext,
) -> Option<Task<Result<(Option<ProjectEntryId>, Box<dyn ItemHandle>)>>>;
}
pub trait ItemHandle: 'static { pub trait ItemHandle: 'static {
fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox; fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox;
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>; fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
@ -840,7 +853,7 @@ impl Workspace {
cx.as_mut().spawn(|mut cx| async move { cx.as_mut().spawn(|mut cx| async move {
let (project_entry_id, project_item) = project_item.await?; let (project_entry_id, project_item) = project_item.await?;
let build_item = cx.update(|cx| { let build_item = cx.update(|cx| {
cx.default_global::<ItemBuilders>() cx.default_global::<ProjectItemBuilders>()
.get(&project_item.model_type()) .get(&project_item.model_type())
.ok_or_else(|| anyhow!("no item builder for project item")) .ok_or_else(|| anyhow!("no item builder for project item"))
.cloned() .cloned()
@ -990,6 +1003,63 @@ impl Workspace {
}); });
} }
pub fn follow(&mut self, leader_id: PeerId, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
if let Some(project_id) = self.project.read(cx).remote_id() {
let request = self.client.request(proto::Follow {
project_id,
leader_id: leader_id.0,
});
cx.spawn_weak(|this, mut cx| async move {
let mut response = request.await?;
if let Some(this) = this.upgrade(&cx) {
let mut item_tasks = Vec::new();
let (project, pane) = this.read_with(&cx, |this, _| {
(this.project.clone(), this.active_pane().clone())
});
for view in &mut response.views {
let variant = view
.variant
.take()
.ok_or_else(|| anyhow!("missing variant"))?;
cx.update(|cx| {
let mut variant = Some(variant);
for build_item in cx.default_global::<FollowedItemBuilders>().clone() {
if let Some(task) =
build_item(pane.clone(), project.clone(), &mut variant, cx)
{
item_tasks.push(task);
break;
} else {
assert!(variant.is_some());
}
}
});
}
let items = futures::future::try_join_all(item_tasks).await?;
let mut items_by_leader_view_id = HashMap::default();
for (view, item) in response.views.into_iter().zip(items) {
items_by_leader_view_id.insert(view.id as usize, item);
}
pane.update(&mut cx, |pane, cx| {
pane.set_follow_state(
FollowerState {
leader_id,
current_view_id: response.current_view_id as usize,
items_by_leader_view_id,
},
cx,
)
})?;
}
Ok(())
})
} else {
Task::ready(Err(anyhow!("project is not remote")))
}
}
fn render_connection_status(&self, cx: &mut RenderContext<Self>) -> Option<ElementBox> { fn render_connection_status(&self, cx: &mut RenderContext<Self>) -> Option<ElementBox> {
let theme = &cx.global::<Settings>().theme; let theme = &cx.global::<Settings>().theme;
match &*self.client.status().borrow() { match &*self.client.status().borrow() {