Detailed changes
@@ -83,7 +83,8 @@ message Envelope {
Follow follow = 72;
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;
}
-message Follow {}
+message Follow {
+ uint64 project_id = 1;
+ uint32 leader_id = 2;
+}
message FollowResponse {
uint64 current_view_id = 1;
repeated View views = 2;
}
-message UpdateFollower {
- uint64 current_view_id = 1;
- repeated ViewUpdate view_updates = 2;
+message UpdateFollowers {
+ uint64 project_id = 1;
+ 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
@@ -147,6 +147,8 @@ messages!(
(BufferSaved, Foreground),
(ChannelMessageSent, Foreground),
(Error, Foreground),
+ (Follow, Foreground),
+ (FollowResponse, Foreground),
(FormatBuffers, Foreground),
(FormatBuffersResponse, Foreground),
(GetChannelMessages, Foreground),
@@ -196,6 +198,7 @@ messages!(
(SendChannelMessageResponse, Foreground),
(ShareProject, Foreground),
(Test, Foreground),
+ (Unfollow, Foreground),
(UnregisterProject, Foreground),
(UnregisterWorktree, Foreground),
(UnshareProject, Foreground),
@@ -203,6 +206,7 @@ messages!(
(UpdateBufferFile, Foreground),
(UpdateContacts, Foreground),
(UpdateDiagnosticSummary, Foreground),
+ (UpdateFollowers, Foreground),
(UpdateWorktree, Foreground),
);
@@ -212,6 +216,7 @@ request_messages!(
ApplyCompletionAdditionalEdits,
ApplyCompletionAdditionalEditsResponse
),
+ (Follow, FollowResponse),
(FormatBuffers, FormatBuffersResponse),
(GetChannelMessages, GetChannelMessagesResponse),
(GetChannels, GetChannelsResponse),
@@ -248,6 +253,7 @@ entity_messages!(
ApplyCompletionAdditionalEdits,
BufferReloaded,
BufferSaved,
+ Follow,
FormatBuffers,
GetCodeActions,
GetCompletions,
@@ -266,11 +272,13 @@ entity_messages!(
SaveBuffer,
SearchProject,
StartLanguageServer,
+ Unfollow,
UnregisterWorktree,
UnshareProject,
UpdateBuffer,
UpdateBufferFile,
UpdateDiagnosticSummary,
+ UpdateFollowers,
UpdateLanguageServer,
RegisterWorktree,
UpdateWorktree,
@@ -1,5 +1,7 @@
use super::{ItemHandle, SplitDirection};
use crate::{Item, Settings, WeakItemHandle, Workspace};
+use anyhow::{anyhow, Result};
+use client::PeerId;
use collections::{HashMap, VecDeque};
use gpui::{
action,
@@ -105,6 +107,13 @@ pub struct Pane {
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 {
fn active_item_changed(
&mut self,
@@ -313,6 +322,21 @@ impl Pane {
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(¤t_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>> {
self.items.iter().map(|(_, view)| view)
}
@@ -7,7 +7,7 @@ pub mod sidebar;
mod status_bar;
use anyhow::{anyhow, Result};
-use client::{Authenticate, ChannelList, Client, User, UserStore};
+use client::{proto, Authenticate, ChannelList, Client, PeerId, User, UserStore};
use clock::ReplicaId;
use collections::HashMap;
use gpui::{
@@ -42,16 +42,18 @@ use std::{
};
use theme::{Theme, ThemeRegistry};
-type ItemBuilders = HashMap<
+type ProjectItemBuilders = HashMap<
TypeId,
- Arc<
- dyn Fn(
- usize,
- ModelHandle<Project>,
- AnyModelHandle,
- &mut MutableAppContext,
- ) -> Box<dyn ItemHandle>,
- >,
+ fn(usize, ModelHandle<Project>, AnyModelHandle, &mut MutableAppContext) -> Box<dyn ItemHandle>,
+>;
+
+type FollowedItemBuilders = Vec<
+ fn(
+ ViewHandle<Pane>,
+ ModelHandle<Project>,
+ &mut Option<proto::view::Variant>,
+ &mut MutableAppContext,
+ ) -> Option<Task<Result<(Option<ProjectEntryId>, Box<dyn ItemHandle>)>>>,
>;
action!(Open, Arc<AppState>);
@@ -108,18 +110,18 @@ pub fn init(cx: &mut MutableAppContext) {
]);
}
-pub fn register_project_item<V>(cx: &mut MutableAppContext)
-where
- V: ProjectItem,
-{
- cx.update_default_global(|builders: &mut ItemBuilders, _| {
- 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_project_item<I: ProjectItem>(cx: &mut MutableAppContext) {
+ cx.update_default_global(|builders: &mut ProjectItemBuilders, _| {
+ builders.insert(TypeId::of::<I::Item>(), |window_id, project, model, cx| {
+ let item = model.downcast::<I::Item>().unwrap();
+ Box::new(cx.add_view(window_id, |cx| I::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;
}
+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 {
fn tab_content(&self, style: &theme::Tab, cx: &AppContext) -> ElementBox;
fn project_path(&self, cx: &AppContext) -> Option<ProjectPath>;
@@ -840,7 +853,7 @@ impl Workspace {
cx.as_mut().spawn(|mut cx| async move {
let (project_entry_id, project_item) = project_item.await?;
let build_item = cx.update(|cx| {
- cx.default_global::<ItemBuilders>()
+ cx.default_global::<ProjectItemBuilders>()
.get(&project_item.model_type())
.ok_or_else(|| anyhow!("no item builder for project item"))
.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> {
let theme = &cx.global::<Settings>().theme;
match &*self.client.status().borrow() {