Allow accessing workspace after adding item to pane

Antonio Scandurra created

Change summary

crates/workspace/src/pane.rs      |  86 +++++++++++++-------------
crates/workspace/src/workspace.rs | 104 ++++++++++++++++++++++----------
2 files changed, 116 insertions(+), 74 deletions(-)

Detailed changes

crates/workspace/src/pane.rs 🔗

@@ -107,12 +107,6 @@ pub struct Pane {
     active_toolbar_visible: bool,
 }
 
-pub(crate) struct FollowerState {
-    pub(crate) leader_id: PeerId,
-    pub(crate) current_view_id: Option<usize>,
-    pub(crate) items_by_leader_view_id: HashMap<usize, Box<dyn ItemHandle>>,
-}
-
 pub trait Toolbar: View {
     fn active_item_changed(
         &mut self,
@@ -266,7 +260,17 @@ impl Pane {
                     if let Some((project_entry_id, build_item)) = task.log_err() {
                         pane.update(&mut cx, |pane, cx| {
                             pane.nav_history.borrow_mut().set_mode(mode);
-                            let item = pane.open_item(project_entry_id, cx, build_item);
+                        });
+                        let item = workspace.update(&mut cx, |workspace, cx| {
+                            Self::open_item(
+                                workspace,
+                                pane.clone(),
+                                project_entry_id,
+                                cx,
+                                build_item,
+                            )
+                        });
+                        pane.update(&mut cx, |pane, cx| {
                             pane.nav_history
                                 .borrow_mut()
                                 .set_mode(NavigationMode::Normal);
@@ -289,52 +293,50 @@ impl Pane {
     }
 
     pub(crate) fn open_item(
-        &mut self,
+        workspace: &mut Workspace,
+        pane: ViewHandle<Pane>,
         project_entry_id: ProjectEntryId,
-        cx: &mut ViewContext<Self>,
+        cx: &mut ViewContext<Workspace>,
         build_item: impl FnOnce(&mut MutableAppContext) -> Box<dyn ItemHandle>,
     ) -> Box<dyn ItemHandle> {
-        for (ix, item) in self.items.iter().enumerate() {
-            if item.project_entry_id(cx) == Some(project_entry_id) {
-                let item = item.boxed_clone();
-                self.activate_item(ix, cx);
-                return item;
+        let existing_item = pane.update(cx, |pane, cx| {
+            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);
+                    return Some(item);
+                }
             }
+            None
+        });
+        if let Some(existing_item) = existing_item {
+            existing_item
+        } else {
+            let item = build_item(cx);
+            Self::add_item(workspace, pane, item.boxed_clone(), cx);
+            item
         }
-
-        let item = build_item(cx);
-        self.add_item(item.boxed_clone(), cx);
-        item
     }
 
-    pub(crate) fn add_item(&mut self, mut item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
+    pub(crate) fn add_item(
+        workspace: &mut Workspace,
+        pane: ViewHandle<Pane>,
+        mut item: Box<dyn ItemHandle>,
+        cx: &mut ViewContext<Workspace>,
+    ) {
         // Prevent adding the same item to the pane more than once.
-        if self.items.iter().any(|i| i.id() == item.id()) {
+        if pane.read(cx).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());
-        self.items.insert(item_idx, item);
-        self.activate_item(item_idx, cx);
-        cx.notify();
-    }
-
-    pub(crate) fn set_follow_state(
-        &mut self,
-        follower_state: FollowerState,
-        cx: &mut ViewContext<Self>,
-    ) -> Result<()> {
-        if let Some(current_view_id) = follower_state.current_view_id {
-            let item = follower_state
-                .items_by_leader_view_id
-                .get(&current_view_id)
-                .ok_or_else(|| anyhow!("invalid current view id"))?
-                .clone();
-            self.add_item(item, cx);
-        }
-        Ok(())
+        item.set_nav_history(pane.read(cx).nav_history.clone(), cx);
+        item.added_to_pane(workspace, pane.clone(), cx);
+        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);
+            cx.notify();
+        });
     }
 
     pub fn items(&self) -> impl Iterator<Item = &Box<dyn ItemHandle>> {

crates/workspace/src/workspace.rs 🔗

@@ -284,7 +284,12 @@ pub trait ItemHandle: 'static {
     fn boxed_clone(&self) -> Box<dyn ItemHandle>;
     fn set_nav_history(&self, nav_history: Rc<RefCell<NavHistory>>, cx: &mut MutableAppContext);
     fn clone_on_split(&self, cx: &mut MutableAppContext) -> Option<Box<dyn ItemHandle>>;
-    fn added_to_pane(&mut self, cx: &mut ViewContext<Pane>);
+    fn added_to_pane(
+        &self,
+        workspace: &mut Workspace,
+        pane: ViewHandle<Pane>,
+        cx: &mut ViewContext<Workspace>,
+    );
     fn deactivated(&self, cx: &mut MutableAppContext);
     fn navigate(&self, data: Box<dyn Any>, cx: &mut MutableAppContext);
     fn id(&self) -> usize;
@@ -350,22 +355,37 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
         })
     }
 
-    fn added_to_pane(&mut self, cx: &mut ViewContext<Pane>) {
-        cx.subscribe(self, |pane, item, event, cx| {
+    fn added_to_pane(
+        &self,
+        workspace: &mut Workspace,
+        pane: ViewHandle<Pane>,
+        cx: &mut ViewContext<Workspace>,
+    ) {
+        let pane = pane.downgrade();
+        cx.subscribe(self, move |workspace, item, event, cx| {
+            let pane = if let Some(pane) = pane.upgrade(cx) {
+                pane
+            } else {
+                log::error!("unexpected item event after pane was dropped");
+                return;
+            };
+
             if T::should_close_item_on_event(event) {
-                pane.close_item(item.id(), cx);
+                pane.update(cx, |pane, cx| pane.close_item(item.id(), cx));
                 return;
             }
 
             if T::should_activate_item_on_event(event) {
-                if let Some(ix) = pane.index_for_item(&item) {
-                    pane.activate_item(ix, cx);
-                    pane.activate(cx);
-                }
+                pane.update(cx, |pane, cx| {
+                    if let Some(ix) = pane.index_for_item(&item) {
+                        pane.activate_item(ix, cx);
+                        pane.activate(cx);
+                    }
+                });
             }
 
             if T::should_update_tab_on_event(event) {
-                cx.notify()
+                pane.update(cx, |_, cx| cx.notify());
             }
 
             if let Some(message) = item
@@ -533,6 +553,7 @@ pub struct Workspace {
     status_bar: ViewHandle<StatusBar>,
     project: ModelHandle<Project>,
     leader_state: LeaderState,
+    follower_states_by_leader: HashMap<PeerId, FollowerState>,
     _observe_current_user: Task<()>,
 }
 
@@ -542,6 +563,11 @@ struct LeaderState {
     subscriptions: Vec<Subscription>,
 }
 
+struct FollowerState {
+    current_view_id: Option<usize>,
+    items_by_leader_view_id: HashMap<usize, Box<dyn ItemHandle>>,
+}
+
 impl Workspace {
     pub fn new(params: &WorkspaceParams, cx: &mut ViewContext<Self>) -> Self {
         cx.observe(&params.project, |_, project, cx| {
@@ -614,6 +640,7 @@ impl Workspace {
             right_sidebar: Sidebar::new(Side::Right),
             project: params.project.clone(),
             leader_state: Default::default(),
+            follower_states_by_leader: Default::default(),
             _observe_current_user,
         };
         this.project_remote_id_changed(this.project.read(cx).remote_id(), cx);
@@ -910,8 +937,8 @@ impl Workspace {
     }
 
     pub fn add_item(&mut self, item: Box<dyn ItemHandle>, cx: &mut ViewContext<Self>) {
-        self.active_pane()
-            .update(cx, |pane, cx| pane.add_item(item, cx))
+        let pane = self.active_pane().clone();
+        Pane::add_item(self, pane, item, cx);
     }
 
     pub fn open_path(
@@ -926,10 +953,14 @@ impl Workspace {
             let pane = pane
                 .upgrade(&cx)
                 .ok_or_else(|| anyhow!("pane was closed"))?;
-            this.update(&mut cx, |_, cx| {
-                pane.update(cx, |pane, cx| {
-                    Ok(pane.open_item(project_entry_id, cx, build_item))
-                })
+            this.update(&mut cx, |this, cx| {
+                Ok(Pane::open_item(
+                    this,
+                    pane,
+                    project_entry_id,
+                    cx,
+                    build_item,
+                ))
             })
         })
     }
@@ -1057,9 +1088,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()) {
-                new_pane.update(cx, |new_pane, cx| {
-                    new_pane.add_item(clone, cx);
-                });
+                Pane::add_item(self, new_pane.clone(), clone, cx);
             }
         }
         self.center.split(&pane, &new_pane, direction).unwrap();
@@ -1149,21 +1178,32 @@ impl Workspace {
                     }
 
                     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.map(|id| id as usize),
-                                items_by_leader_view_id,
-                            },
-                            cx,
+                    let follower_state = FollowerState {
+                        current_view_id: response.current_view_id.map(|id| id as usize),
+                        items_by_leader_view_id: response
+                            .views
+                            .iter()
+                            .map(|v| v.id as usize)
+                            .zip(items)
+                            .collect(),
+                    };
+                    let current_item = if let Some(current_view_id) = follower_state.current_view_id
+                    {
+                        Some(
+                            follower_state
+                                .items_by_leader_view_id
+                                .get(&current_view_id)
+                                .ok_or_else(|| anyhow!("invalid current view id"))?
+                                .clone(),
                         )
-                    })?;
+                    } else {
+                        None
+                    };
+                    this.update(&mut cx, |this, cx| {
+                        if let Some(item) = current_item {
+                            Pane::add_item(this, pane, item, cx);
+                        }
+                    });
                 }
                 Ok(())
             })