diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index ddfe0c00e0..8a3c7452ef 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -84,7 +84,7 @@ use std::{ pub use sum_tree::Bias; use theme::{DiagnosticStyle, Theme}; use util::{post_inc, ResultExt, TryFutureExt}; -use workspace::{ItemNavHistory, Workspace, WorkspaceId}; +use workspace::{ItemNavHistory, ViewId, Workspace, WorkspaceId}; use crate::git::diff_hunk_to_display; @@ -467,6 +467,7 @@ pub struct Editor { keymap_context_layers: BTreeMap, input_enabled: bool, leader_replica_id: Option, + remote_id: Option, hover_state: HoverState, link_go_to_definition_state: LinkGoToDefinitionState, _subscriptions: Vec, @@ -1108,6 +1109,7 @@ impl Editor { keymap_context_layers: Default::default(), input_enabled: true, leader_replica_id: None, + remote_id: None, hover_state: Default::default(), link_go_to_definition_state: Default::default(), _subscriptions: vec![ diff --git a/crates/editor/src/editor_tests.rs b/crates/editor/src/editor_tests.rs index 74fcd27fc3..c3c15bb5b4 100644 --- a/crates/editor/src/editor_tests.rs +++ b/crates/editor/src/editor_tests.rs @@ -3,6 +3,7 @@ use std::{cell::RefCell, rc::Rc, time::Instant}; use drag_and_drop::DragAndDrop; use futures::StreamExt; use indoc::indoc; +use rpc::PeerId; use unindent::Unindent; use super::*; @@ -24,7 +25,7 @@ use util::{ }; use workspace::{ item::{FollowableItem, ItemHandle}, - NavigationEntry, Pane, + NavigationEntry, Pane, ViewId, }; #[gpui::test] @@ -5123,7 +5124,16 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) { let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx)); let follower_1 = cx .update(|cx| { - Editor::from_state_proto(pane.clone(), project.clone(), &mut state_message, cx) + Editor::from_state_proto( + pane.clone(), + project.clone(), + ViewId { + creator: PeerId(0), + id: 0, + }, + &mut state_message, + cx, + ) }) .unwrap() .await @@ -5209,7 +5219,16 @@ async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) { let mut state_message = leader.update(cx, |leader, cx| leader.to_state_proto(cx)); let follower_2 = cx .update(|cx| { - Editor::from_state_proto(pane.clone(), project.clone(), &mut state_message, cx) + Editor::from_state_proto( + pane.clone(), + project.clone(), + ViewId { + creator: PeerId(0), + id: 0, + }, + &mut state_message, + cx, + ) }) .unwrap() .await diff --git a/crates/editor/src/items.rs b/crates/editor/src/items.rs index 6a22c0e289..0057df778b 100644 --- a/crates/editor/src/items.rs +++ b/crates/editor/src/items.rs @@ -28,24 +28,32 @@ use std::{ }; use text::Selection; use util::{ResultExt, TryFutureExt}; +use workspace::item::FollowableItemHandle; use workspace::{ item::{FollowableItem, Item, ItemEvent, ItemHandle, ProjectItem}, searchable::{Direction, SearchEvent, SearchableItem, SearchableItemHandle}, - ItemId, ItemNavHistory, Pane, StatusItemView, ToolbarItemLocation, Workspace, WorkspaceId, + ItemId, ItemNavHistory, Pane, StatusItemView, ToolbarItemLocation, ViewId, Workspace, + WorkspaceId, }; pub const MAX_TAB_TITLE_LEN: usize = 24; impl FollowableItem for Editor { + fn remote_id(&self) -> Option { + self.remote_id + } + fn from_state_proto( pane: ViewHandle, project: ModelHandle, + remote_id: ViewId, state: &mut Option, cx: &mut MutableAppContext, ) -> Option>>> { let Some(proto::view::Variant::Editor(_)) = state else { return None }; let Some(proto::view::Variant::Editor(state)) = state.take() else { unreachable!() }; + let client = project.read(cx).client(); let replica_id = project.read(cx).replica_id(); let buffer_ids = state .excerpts @@ -63,13 +71,13 @@ impl FollowableItem for Editor { let mut buffers = futures::future::try_join_all(buffers).await?; let editor = pane.read_with(&cx, |pane, cx| { let mut editors = pane.items_of_type::(); - if state.singleton && buffers.len() == 1 { - editors.find(|editor| { - editor.read(cx).buffer.read(cx).as_singleton().as_ref() == Some(&buffers[0]) - }) - } else { - None - } + editors.find(|editor| { + editor.remote_id(&client, cx) == Some(remote_id) + || state.singleton + && buffers.len() == 1 + && editor.read(cx).buffer.read(cx).as_singleton().as_ref() + == Some(&buffers[0]) + }) }); let editor = editor.unwrap_or_else(|| { @@ -112,6 +120,7 @@ impl FollowableItem for Editor { }); editor.update(&mut cx, |editor, cx| { + editor.remote_id = Some(remote_id); let buffer = editor.buffer.read(cx).read(cx); let selections = state .selections diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 6bfef6c21a..e951e6f3bf 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -796,7 +796,7 @@ message Follow { } message FollowResponse { - optional uint64 active_view_id = 1; + optional ViewId active_view_id = 1; repeated View views = 2; } @@ -824,13 +824,18 @@ message GetPrivateUserInfoResponse { // Entities +message ViewId { + uint32 creator = 1; + uint64 id = 2; +} + message UpdateActiveView { - optional uint64 id = 1; + optional ViewId id = 1; optional uint32 leader_id = 2; } message UpdateView { - uint64 id = 1; + ViewId id = 1; optional uint32 leader_id = 2; oneof variant { @@ -848,7 +853,7 @@ message UpdateView { } message View { - uint64 id = 1; + ViewId id = 1; optional uint32 leader_id = 2; oneof variant { diff --git a/crates/workspace/src/item.rs b/crates/workspace/src/item.rs index 63394f361d..1d6b4a9eb5 100644 --- a/crates/workspace/src/item.rs +++ b/crates/workspace/src/item.rs @@ -5,12 +5,15 @@ use std::{ fmt, path::PathBuf, rc::Rc, - sync::atomic::{AtomicBool, Ordering}, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, time::Duration, }; use anyhow::Result; -use client::proto; +use client::{proto, Client}; use gpui::{ AnyViewHandle, AppContext, ElementBox, ModelHandle, MutableAppContext, Task, View, ViewContext, ViewHandle, WeakViewHandle, @@ -23,7 +26,8 @@ use util::ResultExt; use crate::{ pane, persistence::model::ItemId, searchable::SearchableItemHandle, DelayedDebouncedEditAction, - FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation, Workspace, WorkspaceId, + FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace, + WorkspaceId, }; #[derive(Eq, PartialEq, Hash)] @@ -278,7 +282,9 @@ impl ItemHandle for ViewHandle { if let Some(message) = followed_item.to_state_proto(cx) { workspace.update_followers( proto::update_followers::Variant::CreateView(proto::View { - id: followed_item.id() as u64, + id: followed_item + .remote_id(&workspace.client, cx) + .map(|id| id.to_proto()), variant: Some(message), leader_id: workspace.leader_for_pane(&pane).map(|id| id.0), }), @@ -332,7 +338,9 @@ impl ItemHandle for ViewHandle { this.update_followers( proto::update_followers::Variant::UpdateView( proto::UpdateView { - id: item.id() as u64, + id: item + .remote_id(&this.client, cx) + .map(|id| id.to_proto()), variant: pending_update.borrow_mut().take(), leader_id: leader_id.map(|id| id.0), }, @@ -584,10 +592,12 @@ pub trait ProjectItem: Item { } pub trait FollowableItem: Item { + fn remote_id(&self) -> Option; fn to_state_proto(&self, cx: &AppContext) -> Option; fn from_state_proto( pane: ViewHandle, project: ModelHandle, + id: ViewId, state: &mut Option, cx: &mut MutableAppContext, ) -> Option>>>; @@ -609,6 +619,7 @@ pub trait FollowableItem: Item { } pub trait FollowableItemHandle: ItemHandle { + fn remote_id(&self, client: &Arc, cx: &AppContext) -> Option; fn set_leader_replica_id(&self, leader_replica_id: Option, cx: &mut MutableAppContext); fn to_state_proto(&self, cx: &AppContext) -> Option; fn add_event_to_update_proto( @@ -627,6 +638,15 @@ pub trait FollowableItemHandle: ItemHandle { } impl FollowableItemHandle for ViewHandle { + fn remote_id(&self, client: &Arc, cx: &AppContext) -> Option { + self.read(cx).remote_id().or_else(|| { + client.peer_id().map(|creator| ViewId { + creator, + id: self.id() as u64, + }) + }) + } + fn set_leader_replica_id(&self, leader_replica_id: Option, cx: &mut MutableAppContext) { self.update(cx, |this, cx| { this.set_leader_replica_id(leader_replica_id, cx) diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index ecc6a43fcc..5cf65568f8 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -318,6 +318,7 @@ pub fn register_project_item(cx: &mut MutableAppContext) { type FollowableItemBuilder = fn( ViewHandle, ModelHandle, + ViewId, &mut Option, &mut MutableAppContext, ) -> Option>>>; @@ -333,8 +334,8 @@ pub fn register_followable_item(cx: &mut MutableAppContext) { builders.insert( TypeId::of::(), ( - |pane, project, state, cx| { - I::from_state_proto(pane, project, state, cx).map(|task| { + |pane, project, id, state, cx| { + I::from_state_proto(pane, project, id, state, cx).map(|task| { cx.foreground() .spawn(async move { Ok(Box::new(task.await?) as Box<_>) }) }) @@ -496,6 +497,12 @@ pub struct Workspace { _observe_current_user: Task<()>, } +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +pub struct ViewId { + pub creator: PeerId, + pub id: u64, +} + #[derive(Default)] struct LeaderState { followers: HashSet, @@ -505,8 +512,8 @@ type FollowerStatesByLeader = HashMap, Follower #[derive(Default)] struct FollowerState { - active_view_id: Option, - items_by_leader_view_id: HashMap>, + active_view_id: Option, + items_by_leader_view_id: HashMap>, } impl Workspace { @@ -1454,7 +1461,11 @@ impl Workspace { self.update_followers( proto::update_followers::Variant::UpdateActiveView(proto::UpdateActiveView { - id: self.active_item(cx).map(|item| item.id() as u64), + id: self.active_item(cx).and_then(|item| { + item.to_followable_item_handle(cx)? + .remote_id(&self.client, cx) + .map(|id| id.to_proto()) + }), leader_id: self.leader_for_pane(&pane).map(|id| id.0), }), cx, @@ -1643,7 +1654,7 @@ impl Workspace { .get_mut(&leader_id) .and_then(|states_by_pane| states_by_pane.get_mut(&pane)) .ok_or_else(|| anyhow!("following interrupted"))?; - state.active_view_id = response.active_view_id; + state.active_view_id = response.active_view_id.map(ViewId::from_proto); Ok::<_, anyhow::Error>(()) })?; Self::add_views_from_leader( @@ -1891,14 +1902,18 @@ impl Workspace { mut cx: AsyncAppContext, ) -> Result { this.update(&mut cx, |this, cx| { + let client = &this.client; this.leader_state .followers .insert(envelope.original_sender_id()?); - let active_view_id = this - .active_item(cx) - .and_then(|i| i.to_followable_item_handle(cx)) - .map(|i| i.id() as u64); + let active_view_id = this.active_item(cx).and_then(|i| { + Some( + i.to_followable_item_handle(cx)? + .remote_id(client, cx)? + .to_proto(), + ) + }); Ok(proto::FollowResponse { active_view_id, views: this @@ -1909,11 +1924,11 @@ impl Workspace { pane.read(cx).items().filter_map({ let cx = &cx; move |item| { - let id = item.id() as u64; let item = item.to_followable_item_handle(cx)?; + let id = item.remote_id(client, cx)?.to_proto(); let variant = item.to_state_proto(cx)?; Some(proto::View { - id, + id: Some(id), leader_id, variant: Some(variant), }) @@ -1964,7 +1979,8 @@ impl Workspace { this.update(cx, |this, _| { if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) { for state in state.values_mut() { - state.active_view_id = update_active_view.id; + state.active_view_id = + update_active_view.id.clone().map(ViewId::from_proto); } } }); @@ -1973,12 +1989,18 @@ impl Workspace { let variant = update_view .variant .ok_or_else(|| anyhow!("missing update view variant"))?; + let id = update_view + .id + .ok_or_else(|| anyhow!("missing update view id"))?; let mut tasks = Vec::new(); this.update(cx, |this, cx| { let project = this.project.clone(); if let Some(state) = this.follower_states_by_leader.get_mut(&leader_id) { for state in state.values_mut() { - if let Some(item) = state.items_by_leader_view_id.get(&update_view.id) { + if let Some(item) = state + .items_by_leader_view_id + .get(&ViewId::from_proto(id.clone())) + { tasks.push(item.apply_update_proto(&project, variant.clone(), cx)); } } @@ -2031,16 +2053,19 @@ impl Workspace { let mut item_tasks = Vec::new(); let mut leader_view_ids = Vec::new(); for view in &views { + let Some(id) = &view.id else { continue }; + let id = ViewId::from_proto(id.clone()); let mut variant = view.variant.clone(); if variant.is_none() { Err(anyhow!("missing variant"))?; } for build_item in &item_builders { - let task = - cx.update(|cx| build_item(pane.clone(), project.clone(), &mut variant, cx)); + let task = cx.update(|cx| { + build_item(pane.clone(), project.clone(), id, &mut variant, cx) + }); if let Some(task) = task { item_tasks.push(task); - leader_view_ids.push(view.id); + leader_view_ids.push(id); break; } else { assert!(variant.is_some()); @@ -2561,6 +2586,22 @@ impl View for Workspace { } } +impl ViewId { + pub(crate) fn from_proto(message: proto::ViewId) -> Self { + Self { + creator: PeerId(message.creator), + id: message.id, + } + } + + pub(crate) fn to_proto(&self) -> proto::ViewId { + proto::ViewId { + creator: self.creator.0, + id: self.id, + } + } +} + pub trait WorkspaceHandle { fn file_project_paths(&self, cx: &AppContext) -> Vec; }