Dedup channel views

Max Brunsfeld and Mikayla created

Co-authored-by: Mikayla <mikayla@zed.dev>

Change summary

crates/collab/src/tests/channel_buffer_tests.rs |  15 -
crates/collab_ui/src/channel_view.rs            | 127 +++++++++++-------
crates/collab_ui/src/collab_panel.rs            |  42 +----
3 files changed, 96 insertions(+), 88 deletions(-)

Detailed changes

crates/collab/src/tests/channel_buffer_tests.rs 🔗

@@ -213,17 +213,12 @@ async fn test_channel_buffer_replica_ids(
         assert_eq!(channel_buffer.buffer().read(cx).replica_id(), 0);
     });
 
-    let channel_window_a = cx_a
-        .add_window(|cx| ChannelView::new(project_a.clone(), channel_buffer_a.clone(), None, cx));
-    let channel_window_b = cx_b
-        .add_window(|cx| ChannelView::new(project_b.clone(), channel_buffer_b.clone(), None, cx));
+    let channel_window_a =
+        cx_a.add_window(|cx| ChannelView::new(project_a.clone(), channel_buffer_a.clone(), cx));
+    let channel_window_b =
+        cx_b.add_window(|cx| ChannelView::new(project_b.clone(), channel_buffer_b.clone(), cx));
     let channel_window_c = cx_c.add_window(|cx| {
-        ChannelView::new(
-            separate_project_c.clone(),
-            channel_buffer_c.clone(),
-            None,
-            cx,
-        )
+        ChannelView::new(separate_project_c.clone(), channel_buffer_c.clone(), cx)
     });
 
     let channel_view_a = channel_window_a.root(cx_a);

crates/collab_ui/src/channel_view.rs 🔗

@@ -1,4 +1,8 @@
-use channel::channel_buffer::{self, ChannelBuffer};
+use anyhow::{anyhow, Result};
+use channel::{
+    channel_buffer::{self, ChannelBuffer},
+    ChannelId,
+};
 use client::proto;
 use clock::ReplicaId;
 use collections::HashMap;
@@ -6,15 +10,13 @@ use editor::Editor;
 use gpui::{
     actions,
     elements::{ChildView, Label},
-    AnyElement, AnyViewHandle, AppContext, Element, Entity, ModelHandle, Subscription, View,
+    AnyElement, AnyViewHandle, AppContext, Element, Entity, ModelHandle, Subscription, Task, View,
     ViewContext, ViewHandle,
 };
-use language::Language;
 use project::Project;
-use std::sync::Arc;
 use workspace::{
     item::{FollowableItem, Item, ItemHandle},
-    register_followable_item, ViewId,
+    register_followable_item, Pane, ViewId, Workspace, WorkspaceId,
 };
 
 actions!(channel_view, [Deploy]);
@@ -32,14 +34,47 @@ pub struct ChannelView {
 }
 
 impl ChannelView {
+    pub fn open(
+        channel_id: ChannelId,
+        pane: ViewHandle<Pane>,
+        workspace: ViewHandle<Workspace>,
+        cx: &mut AppContext,
+    ) -> Task<Result<ViewHandle<Self>>> {
+        let workspace = workspace.read(cx);
+        let project = workspace.project().to_owned();
+        let channel_store = workspace.app_state().channel_store.clone();
+        let markdown = workspace
+            .app_state()
+            .languages
+            .language_for_name("Markdown");
+        let channel_buffer =
+            channel_store.update(cx, |store, cx| store.open_channel_buffer(channel_id, cx));
+
+        cx.spawn(|mut cx| async move {
+            let channel_buffer = channel_buffer.await?;
+            let markdown = markdown.await?;
+            channel_buffer.update(&mut cx, |buffer, cx| {
+                buffer.buffer().update(cx, |buffer, cx| {
+                    buffer.set_language(Some(markdown), cx);
+                })
+            });
+
+            pane.update(&mut cx, |pane, cx| {
+                pane.items_of_type::<Self>()
+                    .find(|channel_view| channel_view.read(cx).channel_buffer == channel_buffer)
+                    .unwrap_or_else(|| cx.add_view(|cx| Self::new(project, channel_buffer, cx)))
+            })
+            .ok_or_else(|| anyhow!("pane was dropped"))
+        })
+    }
+
     pub fn new(
         project: ModelHandle<Project>,
         channel_buffer: ModelHandle<ChannelBuffer>,
-        language: Option<Arc<Language>>,
         cx: &mut ViewContext<Self>,
     ) -> Self {
         let buffer = channel_buffer.read(cx).buffer();
-        buffer.update(cx, |buffer, cx| buffer.set_language(language, cx));
+        // buffer.update(cx, |buffer, cx| buffer.set_language(language, cx));
         let editor = cx.add_view(|cx| Editor::for_buffer(buffer, None, cx));
         let _editor_event_subscription = cx.subscribe(&editor, |_, _, e, cx| cx.emit(e.clone()));
 
@@ -157,6 +192,14 @@ impl Item for ChannelView {
             });
         Label::new(channel_name, style.label.to_owned()).into_any()
     }
+
+    fn clone_on_split(&self, _: WorkspaceId, cx: &mut ViewContext<Self>) -> Option<Self> {
+        Some(Self::new(
+            self.project.clone(),
+            self.channel_buffer.clone(),
+            cx,
+        ))
+    }
 }
 
 impl FollowableItem for ChannelView {
@@ -180,7 +223,7 @@ impl FollowableItem for ChannelView {
     }
 
     fn from_state_proto(
-        _: ViewHandle<workspace::Pane>,
+        pane: ViewHandle<workspace::Pane>,
         workspace: ViewHandle<workspace::Workspace>,
         remote_id: workspace::ViewId,
         state: &mut Option<proto::view::Variant>,
@@ -189,48 +232,38 @@ impl FollowableItem for ChannelView {
         let Some(proto::view::Variant::ChannelView(_)) = state else { return None };
         let Some(proto::view::Variant::ChannelView(state)) = state.take() else { unreachable!() };
 
-        let channel_store = &workspace.read(cx).app_state().channel_store.clone();
-        let open_channel_buffer = channel_store.update(cx, |store, cx| {
-            store.open_channel_buffer(state.channel_id, cx)
-        });
-        let project = workspace.read(cx).project().to_owned();
-        let language = workspace.read(cx).app_state().languages.clone();
-        let get_markdown = language.language_for_name("Markdown");
+        let open = ChannelView::open(state.channel_id, pane, workspace, cx);
 
         Some(cx.spawn(|mut cx| async move {
-            let channel_buffer = open_channel_buffer.await?;
-            let markdown = get_markdown.await?;
-
-            let this = workspace
-                .update(&mut cx, move |_, cx| {
-                    cx.add_view(|cx| {
-                        let mut this = Self::new(project, channel_buffer, Some(markdown), cx);
-                        this.remote_id = Some(remote_id);
-                        this
-                    })
+            let this = open.await?;
+
+            let task = this
+                .update(&mut cx, |this, cx| {
+                    this.remote_id = Some(remote_id);
+
+                    if let Some(state) = state.editor {
+                        Some(this.editor.update(cx, |editor, cx| {
+                            editor.apply_update_proto(
+                                &this.project,
+                                proto::update_view::Variant::Editor(proto::update_view::Editor {
+                                    selections: state.selections,
+                                    pending_selection: state.pending_selection,
+                                    scroll_top_anchor: state.scroll_top_anchor,
+                                    scroll_x: state.scroll_x,
+                                    scroll_y: state.scroll_y,
+                                    ..Default::default()
+                                }),
+                                cx,
+                            )
+                        }))
+                    } else {
+                        None
+                    }
                 })
-                .ok_or_else(|| anyhow::anyhow!("workspace dropped"))?;
-
-            if let Some(state) = state.editor {
-                let task = this.update(&mut cx, |this, cx| {
-                    this.editor.update(cx, |editor, cx| {
-                        editor.apply_update_proto(
-                            &this.project,
-                            proto::update_view::Variant::Editor(proto::update_view::Editor {
-                                selections: state.selections,
-                                pending_selection: state.pending_selection,
-                                scroll_top_anchor: state.scroll_top_anchor,
-                                scroll_x: state.scroll_x,
-                                scroll_y: state.scroll_y,
-                                ..Default::default()
-                            }),
-                            cx,
-                        )
-                    })
-                });
-                if let Some(task) = task {
-                    task.await?;
-                }
+                .ok_or_else(|| anyhow!("window was closed"))?;
+
+            if let Some(task) = task {
+                task.await?;
             }
 
             Ok(this)

crates/collab_ui/src/collab_panel.rs 🔗

@@ -2220,38 +2220,18 @@ impl CollabPanel {
     }
 
     fn open_channel_buffer(&mut self, action: &OpenChannelBuffer, cx: &mut ViewContext<Self>) {
-        let workspace = self.workspace;
-        let open = self.channel_store.update(cx, |channel_store, cx| {
-            channel_store.open_channel_buffer(action.channel_id, cx)
-        });
-
-        cx.spawn(|_, mut cx| async move {
-            let channel_buffer = open.await?;
-
-            let markdown = workspace
-                .read_with(&cx, |workspace, _| {
-                    workspace
-                        .app_state()
-                        .languages
-                        .language_for_name("Markdown")
-                })?
-                .await?;
-
-            workspace.update(&mut cx, |workspace, cx| {
-                let channel_view = cx.add_view(|cx| {
-                    ChannelView::new(
-                        workspace.project().to_owned(),
-                        channel_buffer,
-                        Some(markdown),
-                        cx,
-                    )
+        if let Some(workspace) = self.workspace.upgrade(cx) {
+            let pane = workspace.read(cx).active_pane().clone();
+            let channel_view = ChannelView::open(action.channel_id, pane.clone(), workspace, cx);
+            cx.spawn(|_, mut cx| async move {
+                let channel_view = channel_view.await?;
+                pane.update(&mut cx, |pane, cx| {
+                    pane.add_item(Box::new(channel_view), true, true, None, cx)
                 });
-                workspace.add_item(Box::new(channel_view), cx);
-            })?;
-
-            anyhow::Ok(())
-        })
-        .detach();
+                anyhow::Ok(())
+            })
+            .detach();
+        }
     }
 
     fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext<Self>) {