Reflect leader's view state when recycling existing local editors

Antonio Scandurra created

Change summary

crates/editor/src/items.rs | 61 +++++++++++++++++++++++++++------------
crates/server/src/rpc.rs   | 24 ++++++++++++---
2 files changed, 61 insertions(+), 24 deletions(-)

Detailed changes

crates/editor/src/items.rs 🔗

@@ -36,7 +36,7 @@ impl FollowableItem for Editor {
         });
         Some(cx.spawn(|mut cx| async move {
             let buffer = buffer.await?;
-            Ok(pane
+            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)
@@ -44,25 +44,48 @@ impl FollowableItem for Editor {
                 })
                 .unwrap_or_else(|| {
                     cx.add_view(pane.window_id(), |cx| {
-                        let mut editor = Editor::for_buffer(buffer, Some(project), cx);
-                        let selections = {
-                            let buffer = editor.buffer.read(cx);
-                            let buffer = buffer.read(cx);
-                            let (excerpt_id, buffer_id, _) = buffer.as_singleton().unwrap();
-                            state
-                                .selections
-                                .into_iter()
-                                .filter_map(|selection| {
-                                    deserialize_selection(&excerpt_id, buffer_id, selection)
-                                })
-                                .collect::<Vec<_>>()
-                        };
-                        if !selections.is_empty() {
-                            editor.set_selections(selections.into(), None, false, cx);
-                        }
-                        editor
+                        Editor::for_buffer(buffer, Some(project), cx)
                     })
-                }))
+                });
+            editor.update(&mut cx, |editor, cx| {
+                let excerpt_id;
+                let buffer_id;
+                {
+                    let buffer = editor.buffer.read(cx).read(cx);
+                    let singleton = buffer.as_singleton().unwrap();
+                    excerpt_id = singleton.0.clone();
+                    buffer_id = singleton.1;
+                }
+                let selections = state
+                    .selections
+                    .into_iter()
+                    .map(|selection| {
+                        deserialize_selection(&excerpt_id, buffer_id, selection)
+                            .ok_or_else(|| anyhow!("invalid selection"))
+                    })
+                    .collect::<Result<Vec<_>>>()?;
+                if !selections.is_empty() {
+                    editor.set_selections(selections.into(), None, false, cx);
+                }
+
+                if let Some(anchor) = state.scroll_top {
+                    editor.set_scroll_top_anchor(
+                        Some(Anchor {
+                            buffer_id: Some(state.buffer_id as usize),
+                            excerpt_id: excerpt_id.clone(),
+                            text_anchor: language::proto::deserialize_anchor(anchor)
+                                .ok_or_else(|| anyhow!("invalid scroll top"))?,
+                        }),
+                        false,
+                        cx,
+                    );
+                } else {
+                    editor.set_scroll_top_anchor(None, false, cx);
+                }
+
+                Ok::<_, anyhow::Error>(())
+            })?;
+            Ok(editor)
         }))
     }
 

crates/server/src/rpc.rs 🔗

@@ -1116,7 +1116,7 @@ mod tests {
         },
         time::Duration,
     };
-    use workspace::{Settings, SplitDirection, Workspace, WorkspaceParams};
+    use workspace::{Item, Settings, SplitDirection, Workspace, WorkspaceParams};
 
     #[cfg(test)]
     #[ctor::ctor]
@@ -4287,7 +4287,9 @@ mod tests {
             .downcast::<Editor>()
             .unwrap();
 
-        // Client B starts following client A.
+        // When client B starts following client A, all visible view states are replicated to client B.
+        editor_a1.update(cx_a, |editor, cx| editor.select_ranges([0..1], None, cx));
+        editor_a2.update(cx_a, |editor, cx| editor.select_ranges([2..3], None, cx));
         workspace_b
             .update(cx_b, |workspace, cx| {
                 let leader_id = project_b
@@ -4301,13 +4303,25 @@ mod tests {
             })
             .await
             .unwrap();
-        assert_eq!(
-            workspace_b.read_with(cx_b, |workspace, cx| workspace
+        let editor_b2 = workspace_b.read_with(cx_b, |workspace, cx| {
+            workspace
                 .active_item(cx)
                 .unwrap()
-                .project_path(cx)),
+                .downcast::<Editor>()
+                .unwrap()
+        });
+        assert_eq!(
+            editor_b2.read_with(cx_b, |editor, cx| editor.project_path(cx)),
             Some((worktree_id, "2.txt").into())
         );
+        assert_eq!(
+            editor_b2.read_with(cx_b, |editor, cx| editor.selected_ranges(cx)),
+            vec![2..3]
+        );
+        assert_eq!(
+            editor_b1.read_with(cx_b, |editor, cx| editor.selected_ranges(cx)),
+            vec![0..1]
+        );
 
         // When client A activates a different editor, client B does so as well.
         workspace_a.update(cx_a, |workspace, cx| {