Detailed changes
@@ -494,14 +494,13 @@ impl Client {
message_type_id,
Arc::new(move |handle, envelope, cx| {
if let Some(client) = client.upgrade() {
- let model = handle.downcast::<E>().unwrap();
- let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
let handle = if let AnyEntityHandle::Model(handle) = handle {
handle
} else {
unreachable!();
};
-
+ let model = handle.downcast::<E>().unwrap();
+ let envelope = envelope.into_any().downcast::<TypedEnvelope<M>>().unwrap();
handler(model, *envelope, client.clone(), cx).boxed_local()
} else {
async move { Ok(()) }.boxed_local()
@@ -513,7 +512,7 @@ impl Client {
}
}
- pub fn add_entity_request_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
+ pub fn add_model_request_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
where
M: EntityMessage + RequestMessage,
E: Entity,
@@ -546,6 +545,39 @@ impl Client {
})
}
+ pub fn add_view_request_handler<M, E, H, F>(self: &Arc<Self>, handler: H)
+ where
+ M: EntityMessage + RequestMessage,
+ E: View,
+ H: 'static
+ + Send
+ + Sync
+ + Fn(ViewHandle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
+ F: 'static + Future<Output = Result<M::Response>>,
+ {
+ self.add_view_message_handler(move |view, envelope, client, cx| {
+ let receipt = envelope.receipt();
+ let response = handler(view, envelope, client.clone(), cx);
+ async move {
+ match response.await {
+ Ok(response) => {
+ client.respond(receipt, response)?;
+ Ok(())
+ }
+ Err(error) => {
+ client.respond_with_error(
+ receipt,
+ proto::Error {
+ message: error.to_string(),
+ },
+ )?;
+ Err(error)
+ }
+ }
+ }
+ })
+ }
+
pub fn has_keychain_credentials(&self, cx: &AsyncAppContext) -> bool {
read_credentials_from_keychain(cx).is_some()
}
@@ -341,6 +341,7 @@ pub fn init(cx: &mut MutableAppContext) {
cx.add_async_action(Editor::find_all_references);
workspace::register_project_item::<Editor>(cx);
+ workspace::register_followed_item::<Editor>(cx);
}
trait SelectionExt {
@@ -1,8 +1,8 @@
use crate::{Autoscroll, Editor, Event, NavigationData, ToOffset, ToPoint as _};
-use anyhow::Result;
+use anyhow::{anyhow, Result};
use gpui::{
- elements::*, AppContext, Entity, ModelHandle, RenderContext, Subscription, Task, View,
- ViewContext, ViewHandle,
+ elements::*, AppContext, Entity, ModelHandle, MutableAppContext, RenderContext, Subscription,
+ Task, View, ViewContext, ViewHandle,
};
use language::{Bias, Buffer, Diagnostic, File as _};
use project::{File, Project, ProjectEntryId, ProjectPath};
@@ -19,13 +19,58 @@ impl FollowedItem for Editor {
pane: ViewHandle<workspace::Pane>,
project: ModelHandle<Project>,
state: &mut Option<proto::view::Variant>,
- cx: &mut gpui::MutableAppContext,
+ cx: &mut MutableAppContext,
) -> Option<Task<Result<Box<dyn ItemHandle>>>> {
- todo!()
+ let state = if matches!(state, Some(proto::view::Variant::Editor(_))) {
+ if let Some(proto::view::Variant::Editor(state)) = state.take() {
+ state
+ } else {
+ unreachable!()
+ }
+ } else {
+ return None;
+ };
+
+ let buffer = project.update(cx, |project, cx| {
+ project.open_buffer_by_id(state.buffer_id, cx)
+ });
+ Some(cx.spawn(|mut cx| async move {
+ let buffer = buffer.await?;
+ let editor = pane
+ .read_with(&cx, |pane, cx| {
+ pane.items_of_type::<Self>().find(|editor| {
+ editor.read(cx).buffer.read(cx).as_singleton().as_ref() == Some(&buffer)
+ })
+ })
+ .unwrap_or_else(|| {
+ cx.add_view(pane.window_id(), |cx| {
+ Editor::for_buffer(buffer, Some(project), cx)
+ })
+ });
+ Ok(Box::new(editor) as Box<_>)
+ }))
}
- fn to_state_message(&self, cx: &mut gpui::MutableAppContext) -> proto::view::Variant {
- todo!()
+ fn to_state_message(&self, cx: &AppContext) -> proto::view::Variant {
+ let buffer_id = self
+ .buffer
+ .read(cx)
+ .as_singleton()
+ .unwrap()
+ .read(cx)
+ .remote_id();
+ let selection = self.newest_anchor_selection();
+ let selection = Selection {
+ id: selection.id,
+ start: selection.start.text_anchor.clone(),
+ end: selection.end.text_anchor.clone(),
+ reversed: selection.reversed,
+ goal: Default::default(),
+ };
+ proto::view::Variant::Editor(proto::view::Editor {
+ buffer_id,
+ newest_selection: Some(language::proto::serialize_selection(&selection)),
+ })
}
}
@@ -100,15 +100,16 @@ pub fn serialize_undo_map_entry(
}
pub fn serialize_selections(selections: &Arc<[Selection<Anchor>]>) -> Vec<proto::Selection> {
- selections
- .iter()
- .map(|selection| proto::Selection {
- id: selection.id as u64,
- start: Some(serialize_anchor(&selection.start)),
- end: Some(serialize_anchor(&selection.end)),
- reversed: selection.reversed,
- })
- .collect()
+ selections.iter().map(serialize_selection).collect()
+}
+
+pub fn serialize_selection(selection: &Selection<Anchor>) -> proto::Selection {
+ proto::Selection {
+ id: selection.id as u64,
+ start: Some(serialize_anchor(&selection.start)),
+ end: Some(serialize_anchor(&selection.end)),
+ reversed: selection.reversed,
+ }
}
pub fn serialize_diagnostics<'a>(
@@ -267,21 +267,22 @@ impl Project {
client.add_model_message_handler(Self::handle_update_buffer);
client.add_model_message_handler(Self::handle_update_diagnostic_summary);
client.add_model_message_handler(Self::handle_update_worktree);
- client.add_entity_request_handler(Self::handle_apply_additional_edits_for_completion);
- client.add_entity_request_handler(Self::handle_apply_code_action);
- client.add_entity_request_handler(Self::handle_format_buffers);
- client.add_entity_request_handler(Self::handle_get_code_actions);
- client.add_entity_request_handler(Self::handle_get_completions);
- client.add_entity_request_handler(Self::handle_lsp_command::<GetDefinition>);
- client.add_entity_request_handler(Self::handle_lsp_command::<GetDocumentHighlights>);
- client.add_entity_request_handler(Self::handle_lsp_command::<GetReferences>);
- client.add_entity_request_handler(Self::handle_lsp_command::<PrepareRename>);
- client.add_entity_request_handler(Self::handle_lsp_command::<PerformRename>);
- client.add_entity_request_handler(Self::handle_search_project);
- client.add_entity_request_handler(Self::handle_get_project_symbols);
- client.add_entity_request_handler(Self::handle_open_buffer_for_symbol);
- client.add_entity_request_handler(Self::handle_open_buffer_by_path);
- client.add_entity_request_handler(Self::handle_save_buffer);
+ client.add_model_request_handler(Self::handle_apply_additional_edits_for_completion);
+ client.add_model_request_handler(Self::handle_apply_code_action);
+ client.add_model_request_handler(Self::handle_format_buffers);
+ client.add_model_request_handler(Self::handle_get_code_actions);
+ client.add_model_request_handler(Self::handle_get_completions);
+ client.add_model_request_handler(Self::handle_lsp_command::<GetDefinition>);
+ client.add_model_request_handler(Self::handle_lsp_command::<GetDocumentHighlights>);
+ client.add_model_request_handler(Self::handle_lsp_command::<GetReferences>);
+ client.add_model_request_handler(Self::handle_lsp_command::<PrepareRename>);
+ client.add_model_request_handler(Self::handle_lsp_command::<PerformRename>);
+ client.add_model_request_handler(Self::handle_search_project);
+ client.add_model_request_handler(Self::handle_get_project_symbols);
+ client.add_model_request_handler(Self::handle_open_buffer_for_symbol);
+ client.add_model_request_handler(Self::handle_open_buffer_by_id);
+ client.add_model_request_handler(Self::handle_open_buffer_by_path);
+ client.add_model_request_handler(Self::handle_save_buffer);
}
pub fn local(
@@ -488,7 +489,6 @@ impl Project {
cx.update(|cx| Project::local(client, user_store, languages, fs, cx))
}
- #[cfg(any(test, feature = "test-support"))]
pub fn buffer_for_id(&self, remote_id: u64, cx: &AppContext) -> Option<ModelHandle<Buffer>> {
self.opened_buffers
.get(&remote_id)
@@ -981,6 +981,32 @@ impl Project {
})
}
+ pub fn open_buffer_by_id(
+ &mut self,
+ id: u64,
+ cx: &mut ModelContext<Self>,
+ ) -> Task<Result<ModelHandle<Buffer>>> {
+ if let Some(buffer) = self.buffer_for_id(id, cx) {
+ Task::ready(Ok(buffer))
+ } else if self.is_local() {
+ Task::ready(Err(anyhow!("buffer {} does not exist", id)))
+ } else if let Some(project_id) = self.remote_id() {
+ let request = self
+ .client
+ .request(proto::OpenBufferById { project_id, id });
+ cx.spawn(|this, mut cx| async move {
+ let buffer = request
+ .await?
+ .buffer
+ .ok_or_else(|| anyhow!("invalid buffer"))?;
+ this.update(&mut cx, |this, cx| this.deserialize_buffer(buffer, cx))
+ .await
+ })
+ } else {
+ Task::ready(Err(anyhow!("cannot open buffer while disconnected")))
+ }
+ }
+
pub fn save_buffer_as(
&mut self,
buffer: ModelHandle<Buffer>,
@@ -3889,6 +3915,25 @@ impl Project {
hasher.finalize().as_slice().try_into().unwrap()
}
+ async fn handle_open_buffer_by_id(
+ this: ModelHandle<Self>,
+ envelope: TypedEnvelope<proto::OpenBufferById>,
+ _: Arc<Client>,
+ mut cx: AsyncAppContext,
+ ) -> Result<proto::OpenBufferResponse> {
+ let peer_id = envelope.original_sender_id()?;
+ let buffer = this
+ .update(&mut cx, |this, cx| {
+ this.open_buffer_by_id(envelope.payload.id, cx)
+ })
+ .await?;
+ this.update(&mut cx, |this, cx| {
+ Ok(proto::OpenBufferResponse {
+ buffer: Some(this.serialize_buffer_for_peer(&buffer, peer_id, cx)),
+ })
+ })
+ }
+
async fn handle_open_buffer_by_path(
this: ModelHandle<Self>,
envelope: TypedEnvelope<proto::OpenBufferByPath>,
@@ -544,7 +544,7 @@ message Follow {
}
message FollowResponse {
- uint64 current_view_id = 1;
+ optional uint64 current_view_id = 1;
repeated View views = 2;
}
@@ -92,6 +92,7 @@ impl Server {
.add_request_handler(Server::forward_project_request::<proto::GetDocumentHighlights>)
.add_request_handler(Server::forward_project_request::<proto::GetProjectSymbols>)
.add_request_handler(Server::forward_project_request::<proto::OpenBufferForSymbol>)
+ .add_request_handler(Server::forward_project_request::<proto::OpenBufferById>)
.add_request_handler(Server::forward_project_request::<proto::OpenBufferByPath>)
.add_request_handler(Server::forward_project_request::<proto::GetCompletions>)
.add_request_handler(
@@ -4240,6 +4241,13 @@ mod tests {
})
.await
.unwrap();
+ assert_eq!(
+ workspace_b.read_with(cx_b, |workspace, cx| workspace
+ .active_item(cx)
+ .unwrap()
+ .project_path(cx)),
+ Some((worktree_id, "2.txt").into())
+ );
}
#[gpui::test(iterations = 100)]
@@ -18,6 +18,12 @@ pub struct Selection<T> {
pub goal: SelectionGoal,
}
+impl Default for SelectionGoal {
+ fn default() -> Self {
+ Self::None
+ }
+}
+
impl<T: Clone> Selection<T> {
pub fn head(&self) -> T {
if self.reversed {
@@ -109,7 +109,7 @@ pub struct Pane {
pub(crate) struct FollowerState {
pub(crate) leader_id: PeerId,
- pub(crate) current_view_id: usize,
+ pub(crate) current_view_id: Option<usize>,
pub(crate) items_by_leader_view_id: HashMap<usize, Box<dyn ItemHandle>>,
}
@@ -308,6 +308,11 @@ impl Pane {
}
pub(crate) fn add_item(&mut self, mut item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
+ // Prevent adding the same item to the pane more than once.
+ if self.items.iter().any(|i| i.id() == item.id()) {
+ return;
+ }
+
item.set_nav_history(self.nav_history.clone(), cx);
item.added_to_pane(cx);
let item_idx = cmp::min(self.active_item_index + 1, self.items.len());
@@ -321,13 +326,14 @@ impl Pane {
follower_state: FollowerState,
cx: &mut ViewContext<Self>,
) -> Result<()> {
- let current_view_id = follower_state.current_view_id as usize;
- let item = follower_state
- .items_by_leader_view_id
- .get(¤t_view_id)
- .ok_or_else(|| anyhow!("invalid current view id"))?
- .clone();
- self.add_item(item, cx);
+ if let Some(current_view_id) = follower_state.current_view_id {
+ let item = follower_state
+ .items_by_leader_view_id
+ .get(¤t_view_id)
+ .ok_or_else(|| anyhow!("invalid current view id"))?
+ .clone();
+ self.add_item(item, cx);
+ }
Ok(())
}
@@ -335,6 +341,12 @@ impl Pane {
self.items.iter()
}
+ pub fn items_of_type<'a, T: View>(&'a self) -> impl 'a + Iterator<Item = ViewHandle<T>> {
+ self.items
+ .iter()
+ .filter_map(|item| item.to_any().downcast())
+ }
+
pub fn active_item(&self) -> Option<Box<dyn ItemHandle>> {
self.items.get(self.active_item_index).cloned()
}
@@ -111,8 +111,8 @@ pub fn init(client: &Arc<Client>, cx: &mut MutableAppContext) {
),
]);
- client.add_entity_request_handler(Workspace::handle_follow);
- client.add_model_message_handler(Workspace::handle_unfollow);
+ client.add_view_request_handler(Workspace::handle_follow);
+ client.add_view_message_handler(Workspace::handle_unfollow);
}
pub fn register_project_item<I: ProjectItem>(cx: &mut MutableAppContext) {
@@ -235,7 +235,7 @@ pub trait FollowedItem {
where
Self: Sized;
- fn to_state_message(&self, cx: &mut MutableAppContext) -> proto::view::Variant;
+ fn to_state_message(&self, cx: &AppContext) -> proto::view::Variant;
}
pub trait ItemHandle: 'static {
@@ -262,6 +262,8 @@ pub trait ItemHandle: 'static {
cx: &mut MutableAppContext,
) -> Task<Result<()>>;
fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle>;
+ fn can_be_followed(&self, cx: &AppContext) -> bool;
+ fn to_state_message(&self, cx: &AppContext) -> Option<proto::view::Variant>;
}
pub trait WeakItemHandle {
@@ -297,11 +299,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
Box::new(self.clone())
}
- fn clone_on_split(
- &self,
- // nav_history: Rc<RefCell<NavHistory>>,
- cx: &mut MutableAppContext,
- ) -> Option<Box<dyn ItemHandle>> {
+ fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>> {
self.update(cx, |item, cx| {
cx.add_option_view(|cx| item.clone_on_split(cx))
})
@@ -381,6 +379,16 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
fn act_as_type(&self, type_id: TypeId, cx: &AppContext) -> Option<AnyViewHandle> {
self.read(cx).act_as_type(type_id, self, cx)
}
+
+ fn can_be_followed(&self, cx: &AppContext) -> bool {
+ self.read(cx).as_followed().is_some()
+ }
+
+ fn to_state_message(&self, cx: &AppContext) -> Option<proto::view::Variant> {
+ self.read(cx)
+ .as_followed()
+ .map(|item| item.to_state_message(cx))
+ }
}
impl Into<AnyViewHandle> for Box<dyn ItemHandle> {
@@ -709,6 +717,13 @@ impl Workspace {
}
}
+ pub fn items<'a>(
+ &'a self,
+ cx: &'a AppContext,
+ ) -> impl 'a + Iterator<Item = &Box<dyn ItemHandle>> {
+ self.panes.iter().flat_map(|pane| pane.read(cx).items())
+ }
+
pub fn item_of_type<T: Item>(&self, cx: &AppContext) -> Option<ViewHandle<T>> {
self.items_of_type(cx).max_by_key(|item| item.id())
}
@@ -717,11 +732,9 @@ impl Workspace {
&'a self,
cx: &'a AppContext,
) -> impl 'a + Iterator<Item = ViewHandle<T>> {
- self.panes.iter().flat_map(|pane| {
- pane.read(cx)
- .items()
- .filter_map(|item| item.to_any().downcast())
- })
+ self.panes
+ .iter()
+ .flat_map(|pane| pane.read(cx).items_of_type())
}
pub fn active_item(&self, cx: &AppContext) -> Option<Box<dyn ItemHandle>> {
@@ -1085,7 +1098,7 @@ impl Workspace {
pane.set_follow_state(
FollowerState {
leader_id,
- current_view_id: response.current_view_id as usize,
+ current_view_id: response.current_view_id.map(|id| id as usize),
items_by_leader_view_id,
},
cx,
@@ -1310,13 +1323,33 @@ impl Workspace {
async fn handle_follow(
this: ViewHandle<Self>,
- envelope: TypedEnvelope<proto::Follow>,
+ _: TypedEnvelope<proto::Follow>,
_: Arc<Client>,
cx: AsyncAppContext,
) -> Result<proto::FollowResponse> {
- Ok(proto::FollowResponse {
- current_view_id: 0,
- views: Default::default(),
+ this.read_with(&cx, |this, cx| {
+ let current_view_id = if let Some(active_item) = this.active_item(cx) {
+ if active_item.can_be_followed(cx) {
+ Some(active_item.id() as u64)
+ } else {
+ None
+ }
+ } else {
+ None
+ };
+ Ok(proto::FollowResponse {
+ current_view_id,
+ views: this
+ .items(cx)
+ .filter_map(|item| {
+ let variant = item.to_state_message(cx)?;
+ Some(proto::View {
+ id: item.id() as u64,
+ variant: Some(variant),
+ })
+ })
+ .collect(),
+ })
})
}