Stop following when activating a different item on the follower pane

Antonio Scandurra created

Change summary

crates/server/src/rpc.rs          | 38 +++++++++++++++++++++++++++++++++
crates/workspace/src/pane.rs      | 19 +++++++++------
crates/workspace/src/workspace.rs | 15 ++++++++----
3 files changed, 59 insertions(+), 13 deletions(-)

Detailed changes

crates/server/src/rpc.rs 🔗

@@ -4578,6 +4578,44 @@ mod tests {
             workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
             None
         );
+
+        workspace_b
+            .update(cx_b, |workspace, cx| {
+                workspace.toggle_follow(&leader_id.into(), cx).unwrap()
+            })
+            .await
+            .unwrap();
+        assert_eq!(
+            workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+            Some(leader_id)
+        );
+
+        // When client B activates a different pane, it continues following client A in the original pane.
+        workspace_b.update(cx_b, |workspace, cx| {
+            workspace.split_pane(pane_b.clone(), SplitDirection::Right, cx)
+        });
+        assert_eq!(
+            workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+            Some(leader_id)
+        );
+
+        workspace_b.update(cx_b, |workspace, cx| workspace.activate_next_pane(cx));
+        assert_eq!(
+            workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+            Some(leader_id)
+        );
+
+        // When client B activates a different item in the original pane, it automatically stops following client A.
+        workspace_b
+            .update(cx_b, |workspace, cx| {
+                workspace.open_path((worktree_id, "2.txt"), cx)
+            })
+            .await
+            .unwrap();
+        assert_eq!(
+            workspace_b.read_with(cx_b, |workspace, _| workspace.leader_for_pane(&pane_b)),
+            None
+        );
     }
 
     #[gpui::test(iterations = 100)]

crates/workspace/src/pane.rs 🔗

@@ -33,7 +33,7 @@ const MAX_NAVIGATION_HISTORY_LEN: usize = 1024;
 
 pub fn init(cx: &mut MutableAppContext) {
     cx.add_action(|pane: &mut Pane, action: &ActivateItem, cx| {
-        pane.activate_item(action.0, cx);
+        pane.activate_item(action.0, true, cx);
     });
     cx.add_action(|pane: &mut Pane, _: &ActivatePrevItem, cx| {
         pane.activate_prev_item(cx);
@@ -92,6 +92,7 @@ pub fn init(cx: &mut MutableAppContext) {
 
 pub enum Event {
     Activate,
+    ActivateItem { local: bool },
     Remove,
     Split(SplitDirection),
 }
@@ -301,7 +302,7 @@ impl Pane {
             for (ix, item) in pane.items.iter().enumerate() {
                 if item.project_entry_id(cx) == Some(project_entry_id) {
                     let item = item.boxed_clone();
-                    pane.activate_item(ix, cx);
+                    pane.activate_item(ix, true, cx);
                     return Some(item);
                 }
             }
@@ -311,7 +312,7 @@ impl Pane {
             existing_item
         } else {
             let item = build_item(cx);
-            Self::add_item(workspace, pane, item.boxed_clone(), cx);
+            Self::add_item(workspace, pane, item.boxed_clone(), true, cx);
             item
         }
     }
@@ -320,11 +321,12 @@ impl Pane {
         workspace: &mut Workspace,
         pane: ViewHandle<Pane>,
         item: Box<dyn ItemHandle>,
+        local: bool,
         cx: &mut ViewContext<Workspace>,
     ) {
         // Prevent adding the same item to the pane more than once.
         if let Some(item_ix) = pane.read(cx).items.iter().position(|i| i.id() == item.id()) {
-            pane.update(cx, |pane, cx| pane.activate_item(item_ix, cx));
+            pane.update(cx, |pane, cx| pane.activate_item(item_ix, local, cx));
             return;
         }
 
@@ -333,7 +335,7 @@ impl Pane {
         pane.update(cx, |pane, cx| {
             let item_idx = cmp::min(pane.active_item_index + 1, pane.items.len());
             pane.items.insert(item_idx, item);
-            pane.activate_item(item_idx, cx);
+            pane.activate_item(item_idx, local, cx);
             cx.notify();
         });
     }
@@ -384,13 +386,14 @@ impl Pane {
         self.items.iter().position(|i| i.id() == item.id())
     }
 
-    pub fn activate_item(&mut self, index: usize, cx: &mut ViewContext<Self>) {
+    pub fn activate_item(&mut self, index: usize, local: bool, cx: &mut ViewContext<Self>) {
         if index < self.items.len() {
             let prev_active_item_ix = mem::replace(&mut self.active_item_index, index);
             if prev_active_item_ix != self.active_item_index
                 && prev_active_item_ix < self.items.len()
             {
                 self.items[prev_active_item_ix].deactivated(cx);
+                cx.emit(Event::ActivateItem { local });
             }
             self.update_active_toolbar(cx);
             self.focus_active_item(cx);
@@ -406,7 +409,7 @@ impl Pane {
         } else if self.items.len() > 0 {
             index = self.items.len() - 1;
         }
-        self.activate_item(index, cx);
+        self.activate_item(index, true, cx);
     }
 
     pub fn activate_next_item(&mut self, cx: &mut ViewContext<Self>) {
@@ -416,7 +419,7 @@ impl Pane {
         } else {
             index = 0;
         }
-        self.activate_item(index, cx);
+        self.activate_item(index, true, cx);
     }
 
     pub fn close_active_item(&mut self, cx: &mut ViewContext<Self>) {

crates/workspace/src/workspace.rs 🔗

@@ -445,7 +445,7 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
             if T::should_activate_item_on_event(event) {
                 pane.update(cx, |pane, cx| {
                     if let Some(ix) = pane.index_for_item(&item) {
-                        pane.activate_item(ix, cx);
+                        pane.activate_item(ix, true, cx);
                         pane.activate(cx);
                     }
                 });
@@ -1022,7 +1022,7 @@ impl Workspace {
 
     pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
         let pane = self.active_pane().clone();
-        Pane::add_item(self, pane, item, cx);
+        Pane::add_item(self, pane, item, true, cx);
     }
 
     pub fn open_path(
@@ -1111,7 +1111,7 @@ impl Workspace {
         });
         if let Some((pane, ix)) = result {
             self.activate_pane(pane.clone(), cx);
-            pane.update(cx, |pane, cx| pane.activate_item(ix, cx));
+            pane.update(cx, |pane, cx| pane.activate_item(ix, true, cx));
             true
         } else {
             false
@@ -1164,6 +1164,11 @@ impl Workspace {
                 pane::Event::Activate => {
                     self.activate_pane(pane, cx);
                 }
+                pane::Event::ActivateItem { local } => {
+                    if *local {
+                        self.unfollow(&pane, cx);
+                    }
+                }
             }
         } else {
             error!("pane {} not found", pane_id);
@@ -1180,7 +1185,7 @@ impl Workspace {
         self.activate_pane(new_pane.clone(), cx);
         if let Some(item) = pane.read(cx).active_item() {
             if let Some(clone) = item.clone_on_split(cx.as_mut()) {
-                Pane::add_item(self, new_pane.clone(), clone, cx);
+                Pane::add_item(self, new_pane.clone(), clone, true, cx);
             }
         }
         self.center.split(&pane, &new_pane, direction).unwrap();
@@ -1793,7 +1798,7 @@ impl Workspace {
         }
 
         for (pane, item) in items_to_add {
-            Pane::add_item(self, pane.clone(), item.boxed_clone(), cx);
+            Pane::add_item(self, pane.clone(), item.boxed_clone(), false, cx);
             cx.notify();
         }
         None