Restrict DAG-related functionality, but retain infrastructure for implementing symlinks

Mikayla created

Change summary

crates/channel/src/channel_store_tests.rs |  16 
crates/collab/src/db/queries/channels.rs  |  52 +++
crates/collab/src/rpc.rs                  | 125 ++++----
crates/collab/src/tests/channel_tests.rs  | 371 ++++++++++++------------
crates/collab_ui/src/collab_panel.rs      | 188 ++---------
crates/rpc/proto/zed.proto                |   6 
6 files changed, 351 insertions(+), 407 deletions(-)

Detailed changes

crates/channel/src/channel_store_tests.rs 🔗

@@ -19,17 +19,15 @@ fn test_update_channels(cx: &mut AppContext) {
                     id: 1,
                     name: "b".to_string(),
                     visibility: proto::ChannelVisibility::Members as i32,
+                    role: proto::ChannelRole::Admin.into(),
                 },
                 proto::Channel {
                     id: 2,
                     name: "a".to_string(),
                     visibility: proto::ChannelVisibility::Members as i32,
+                    role: proto::ChannelRole::Member.into(),
                 },
             ],
-            channel_permissions: vec![proto::ChannelPermission {
-                channel_id: 1,
-                role: proto::ChannelRole::Admin.into(),
-            }],
             ..Default::default()
         },
         cx,
@@ -52,11 +50,13 @@ fn test_update_channels(cx: &mut AppContext) {
                     id: 3,
                     name: "x".to_string(),
                     visibility: proto::ChannelVisibility::Members as i32,
+                    role: proto::ChannelRole::Member.into(),
                 },
                 proto::Channel {
                     id: 4,
                     name: "y".to_string(),
                     visibility: proto::ChannelVisibility::Members as i32,
+                    role: proto::ChannelRole::Member.into(),
                 },
             ],
             insert_edge: vec![
@@ -97,16 +97,19 @@ fn test_dangling_channel_paths(cx: &mut AppContext) {
                     id: 0,
                     name: "a".to_string(),
                     visibility: proto::ChannelVisibility::Members as i32,
+                    role: proto::ChannelRole::Admin.into(),
                 },
                 proto::Channel {
                     id: 1,
                     name: "b".to_string(),
                     visibility: proto::ChannelVisibility::Members as i32,
+                    role: proto::ChannelRole::Admin.into(),
                 },
                 proto::Channel {
                     id: 2,
                     name: "c".to_string(),
                     visibility: proto::ChannelVisibility::Members as i32,
+                    role: proto::ChannelRole::Admin.into(),
                 },
             ],
             insert_edge: vec![
@@ -119,10 +122,6 @@ fn test_dangling_channel_paths(cx: &mut AppContext) {
                     channel_id: 2,
                 },
             ],
-            channel_permissions: vec![proto::ChannelPermission {
-                channel_id: 0,
-                role: proto::ChannelRole::Admin.into(),
-            }],
             ..Default::default()
         },
         cx,
@@ -166,6 +165,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
             id: channel_id,
             name: "the-channel".to_string(),
             visibility: proto::ChannelVisibility::Members as i32,
+            role: proto::ChannelRole::Admin.into(),
         }],
         ..Default::default()
     });

crates/collab/src/db/queries/channels.rs 🔗

@@ -419,7 +419,7 @@ impl Database {
             }
 
             let channels = channel::Entity::find()
-                .filter(channel::Column::Id.is_in(role_for_channel.keys().cloned()))
+                .filter(channel::Column::Id.is_in(role_for_channel.keys().copied()))
                 .all(&*tx)
                 .await?;
 
@@ -633,6 +633,36 @@ impl Database {
             .await
     }
 
+    pub async fn get_channel_members_and_roles(
+        &self,
+        id: ChannelId,
+    ) -> Result<Vec<(UserId, ChannelRole)>> {
+        self.transaction(|tx| async move {
+            #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
+            enum QueryUserIdsAndRoles {
+                UserId,
+                Role,
+            }
+
+            let ancestor_ids = self.get_channel_ancestors(id, &*tx).await?;
+            let user_ids_and_roles = channel_member::Entity::find()
+                .distinct()
+                .filter(
+                    channel_member::Column::ChannelId
+                        .is_in(ancestor_ids.iter().copied())
+                        .and(channel_member::Column::Accepted.eq(true)),
+                )
+                .select_only()
+                .column(channel_member::Column::UserId)
+                .column(channel_member::Column::Role)
+                .into_values::<_, QueryUserIdsAndRoles>()
+                .all(&*tx)
+                .await?;
+            Ok(user_ids_and_roles)
+        })
+        .await
+    }
+
     pub async fn set_channel_member_role(
         &self,
         channel_id: ChannelId,
@@ -1138,9 +1168,6 @@ impl Database {
         to: ChannelId,
     ) -> Result<ChannelGraph> {
         self.transaction(|tx| async move {
-            // Note that even with these maxed permissions, this linking operation
-            // is still insecure because you can't remove someone's permissions to a
-            // channel if they've linked the channel to one where they're an admin.
             self.check_user_is_channel_admin(channel, user, &*tx)
                 .await?;
 
@@ -1327,6 +1354,23 @@ impl Database {
         })
         .await
     }
+
+    pub async fn assert_root_channel(&self, channel: ChannelId) -> Result<()> {
+        self.transaction(|tx| async move {
+            let path = channel_path::Entity::find()
+                .filter(channel_path::Column::ChannelId.eq(channel))
+                .one(&*tx)
+                .await?
+                .ok_or_else(|| anyhow!("no such channel found"))?;
+
+            let mut id_parts = path.id_path.trim_matches('/').split('/');
+
+            (id_parts.next().is_some() && id_parts.next().is_none())
+                .then_some(())
+                .ok_or_else(|| anyhow!("channel is not a root channel").into())
+        })
+        .await
+    }
 }
 
 #[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]

crates/collab/src/rpc.rs 🔗

@@ -3,8 +3,8 @@ mod connection_pool;
 use crate::{
     auth,
     db::{
-        self, BufferId, ChannelId, ChannelsForUser, Database, MessageId, ProjectId, RoomId,
-        ServerId, User, UserId,
+        self, BufferId, ChannelId, ChannelRole, ChannelVisibility, ChannelsForUser, Database,
+        MessageId, ProjectId, RoomId, ServerId, User, UserId,
     },
     executor::Executor,
     AppState, Result,
@@ -38,8 +38,8 @@ use lazy_static::lazy_static;
 use prometheus::{register_int_gauge, IntGauge};
 use rpc::{
     proto::{
-        self, Ack, AnyTypedEnvelope, ChannelEdge, EntityMessage, EnvelopedMessage,
-        LiveKitConnectionInfo, RequestMessage, UpdateChannelBufferCollaborators,
+        self, Ack, AnyTypedEnvelope, EntityMessage, EnvelopedMessage, LiveKitConnectionInfo,
+        RequestMessage, UpdateChannelBufferCollaborators,
     },
     Connection, ConnectionId, Peer, Receipt, TypedEnvelope,
 };
@@ -2366,15 +2366,18 @@ async fn set_channel_visibility(
     for member in members {
         for connection_id in connection_pool.user_connection_ids(UserId::from_proto(member.user_id))
         {
-            let mut update = proto::UpdateChannels::default();
-            update.channels.push(proto::Channel {
-                id: channel.id.to_proto(),
-                name: channel.name.clone(),
-                visibility: channel.visibility.into(),
-                role: member.role.into(),
-            });
-
-            session.peer.send(connection_id, update.clone())?;
+            session.peer.send(
+                connection_id,
+                proto::UpdateChannels {
+                    channels: vec![proto::Channel {
+                        id: channel.id.to_proto(),
+                        name: channel.name.clone(),
+                        visibility: channel.visibility.into(),
+                        role: member.role.into(),
+                    }],
+                    ..Default::default()
+                },
+            )?;
         }
     }
 
@@ -2468,6 +2471,8 @@ async fn rename_channel(
     Ok(())
 }
 
+// TODO: Implement in terms of symlinks
+// Current behavior of this is more like 'Move root channel'
 async fn link_channel(
     request: proto::LinkChannel,
     response: Response<proto::LinkChannel>,
@@ -2476,30 +2481,46 @@ async fn link_channel(
     let db = session.db().await;
     let channel_id = ChannelId::from_proto(request.channel_id);
     let to = ChannelId::from_proto(request.to);
-    let channels_to_send = db.link_channel(session.user_id, channel_id, to).await?;
 
-    let members = db.get_channel_members(to).await?;
+    // TODO: Remove this restriction once we have symlinks
+    db.assert_root_channel(channel_id).await?;
+
+    let channels_to_send = db.link_channel(session.user_id, channel_id, to).await?;
+    let members = db.get_channel_members_and_roles(to).await?;
     let connection_pool = session.connection_pool().await;
-    let update = proto::UpdateChannels {
-        channels: channels_to_send
-            .channels
-            .into_iter()
-            .map(|channel| proto::Channel {
-                id: channel.id.to_proto(),
-                visibility: channel.visibility.into(),
-                name: channel.name,
-                // TODO: not all these members should be able to see all those channels
-                // the channels in channels_to_send are from the admin point of view,
-                // but any public guests should only get updates about public channels.
-                role: todo!(),
-            })
-            .collect(),
-        insert_edge: channels_to_send.edges,
-        ..Default::default()
-    };
-    for member_id in members {
+
+    for (member_id, role) in members {
+        let build_channel_proto = |channel: &db::Channel| proto::Channel {
+            id: channel.id.to_proto(),
+            visibility: channel.visibility.into(),
+            name: channel.name.clone(),
+            role: role.into(),
+        };
+
         for connection_id in connection_pool.user_connection_ids(member_id) {
-            session.peer.send(connection_id, update.clone())?;
+            let channels: Vec<_> = if role == ChannelRole::Guest {
+                channels_to_send
+                    .channels
+                    .iter()
+                    .filter(|channel| channel.visibility != ChannelVisibility::Public)
+                    .map(build_channel_proto)
+                    .collect()
+            } else {
+                channels_to_send
+                    .channels
+                    .iter()
+                    .map(build_channel_proto)
+                    .collect()
+            };
+
+            session.peer.send(
+                connection_id,
+                proto::UpdateChannels {
+                    channels,
+                    insert_edge: channels_to_send.edges.clone(),
+                    ..Default::default()
+                },
+            )?;
         }
     }
 
@@ -2508,36 +2529,13 @@ async fn link_channel(
     Ok(())
 }
 
+// TODO: Implement in terms of symlinks
 async fn unlink_channel(
-    request: proto::UnlinkChannel,
-    response: Response<proto::UnlinkChannel>,
-    session: Session,
+    _request: proto::UnlinkChannel,
+    _response: Response<proto::UnlinkChannel>,
+    _session: Session,
 ) -> Result<()> {
-    let db = session.db().await;
-    let channel_id = ChannelId::from_proto(request.channel_id);
-    let from = ChannelId::from_proto(request.from);
-
-    db.unlink_channel(session.user_id, channel_id, from).await?;
-
-    let members = db.get_channel_members(from).await?;
-
-    let update = proto::UpdateChannels {
-        delete_edge: vec![proto::ChannelEdge {
-            channel_id: channel_id.to_proto(),
-            parent_id: from.to_proto(),
-        }],
-        ..Default::default()
-    };
-    let connection_pool = session.connection_pool().await;
-    for member_id in members {
-        for connection_id in connection_pool.user_connection_ids(member_id) {
-            session.peer.send(connection_id, update.clone())?;
-        }
-    }
-
-    response.send(Ack {})?;
-
-    Ok(())
+    Err(anyhow!("unimplemented").into())
 }
 
 async fn move_channel(
@@ -2554,7 +2552,7 @@ async fn move_channel(
         .public_path_to_channel(from_parent)
         .await?
         .last()
-        .cloned();
+        .copied();
 
     let channels_to_send = db
         .move_channel(session.user_id, channel_id, from_parent, to)
@@ -2574,6 +2572,7 @@ async fn move_channel(
         .filter(|member| {
             member.role() == proto::ChannelRole::Admin || member.role() == proto::ChannelRole::Guest
         });
+
     let members_to = db
         .get_channel_participant_details(to, session.user_id)
         .await?

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

@@ -1031,14 +1031,14 @@ async fn test_invite_access(
 async fn test_channel_moving(
     deterministic: Arc<Deterministic>,
     cx_a: &mut TestAppContext,
-    cx_b: &mut TestAppContext,
-    cx_c: &mut TestAppContext,
+    _cx_b: &mut TestAppContext,
+    _cx_c: &mut TestAppContext,
 ) {
     deterministic.forbid_parking();
     let mut server = TestServer::start(&deterministic).await;
     let client_a = server.create_client(cx_a, "user_a").await;
-    let client_b = server.create_client(cx_b, "user_b").await;
-    let client_c = server.create_client(cx_c, "user_c").await;
+    // let client_b = server.create_client(cx_b, "user_b").await;
+    // let client_c = server.create_client(cx_c, "user_c").await;
 
     let channels = server
         .make_channel_tree(
@@ -1091,187 +1091,188 @@ async fn test_channel_moving(
         ],
     );
 
-    client_a
-        .channel_store()
-        .update(cx_a, |channel_store, cx| {
-            channel_store.link_channel(channel_d_id, channel_c_id, cx)
-        })
-        .await
-        .unwrap();
-
-    // Current shape for A:
-    //      /------\
-    // a - b -- c -- d
-    assert_channels_list_shape(
-        client_a.channel_store(),
-        cx_a,
-        &[
-            (channel_a_id, 0),
-            (channel_b_id, 1),
-            (channel_c_id, 2),
-            (channel_d_id, 3),
-            (channel_d_id, 2),
-        ],
-    );
-
-    let b_channels = server
-        .make_channel_tree(
-            &[
-                ("channel-mu", None),
-                ("channel-gamma", Some("channel-mu")),
-                ("channel-epsilon", Some("channel-mu")),
-            ],
-            (&client_b, cx_b),
-        )
-        .await;
-    let channel_mu_id = b_channels[0];
-    let channel_ga_id = b_channels[1];
-    let channel_ep_id = b_channels[2];
-
-    // Current shape for B:
-    //    /- ep
-    // mu -- ga
-    assert_channels_list_shape(
-        client_b.channel_store(),
-        cx_b,
-        &[(channel_mu_id, 0), (channel_ep_id, 1), (channel_ga_id, 1)],
-    );
-
-    client_a
-        .add_admin_to_channel((&client_b, cx_b), channel_b_id, cx_a)
-        .await;
-
-    // Current shape for B:
-    //    /- ep
-    // mu -- ga
-    //  /---------\
-    // b  -- c  -- d
-    assert_channels_list_shape(
-        client_b.channel_store(),
-        cx_b,
-        &[
-            // New channels from a
-            (channel_b_id, 0),
-            (channel_c_id, 1),
-            (channel_d_id, 2),
-            (channel_d_id, 1),
-            // B's old channels
-            (channel_mu_id, 0),
-            (channel_ep_id, 1),
-            (channel_ga_id, 1),
-        ],
-    );
-
-    client_b
-        .add_admin_to_channel((&client_c, cx_c), channel_ep_id, cx_b)
-        .await;
-
-    // Current shape for C:
-    // - ep
-    assert_channels_list_shape(client_c.channel_store(), cx_c, &[(channel_ep_id, 0)]);
-
-    client_b
-        .channel_store()
-        .update(cx_b, |channel_store, cx| {
-            channel_store.link_channel(channel_b_id, channel_ep_id, cx)
-        })
-        .await
-        .unwrap();
-
-    // Current shape for B:
-    //              /---------\
-    //    /- ep -- b  -- c  -- d
-    // mu -- ga
-    assert_channels_list_shape(
-        client_b.channel_store(),
-        cx_b,
-        &[
-            (channel_mu_id, 0),
-            (channel_ep_id, 1),
-            (channel_b_id, 2),
-            (channel_c_id, 3),
-            (channel_d_id, 4),
-            (channel_d_id, 3),
-            (channel_ga_id, 1),
-        ],
-    );
-
-    // Current shape for C:
-    //        /---------\
-    // ep -- b  -- c  -- d
-    assert_channels_list_shape(
-        client_c.channel_store(),
-        cx_c,
-        &[
-            (channel_ep_id, 0),
-            (channel_b_id, 1),
-            (channel_c_id, 2),
-            (channel_d_id, 3),
-            (channel_d_id, 2),
-        ],
-    );
-
-    client_b
-        .channel_store()
-        .update(cx_b, |channel_store, cx| {
-            channel_store.link_channel(channel_ga_id, channel_b_id, cx)
-        })
-        .await
-        .unwrap();
-
-    // Current shape for B:
-    //              /---------\
-    //    /- ep -- b  -- c  -- d
-    //   /          \
-    // mu ---------- ga
-    assert_channels_list_shape(
-        client_b.channel_store(),
-        cx_b,
-        &[
-            (channel_mu_id, 0),
-            (channel_ep_id, 1),
-            (channel_b_id, 2),
-            (channel_c_id, 3),
-            (channel_d_id, 4),
-            (channel_d_id, 3),
-            (channel_ga_id, 3),
-            (channel_ga_id, 1),
-        ],
-    );
-
-    // Current shape for A:
-    //      /------\
-    // a - b -- c -- d
-    //      \-- ga
-    assert_channels_list_shape(
-        client_a.channel_store(),
-        cx_a,
-        &[
-            (channel_a_id, 0),
-            (channel_b_id, 1),
-            (channel_c_id, 2),
-            (channel_d_id, 3),
-            (channel_d_id, 2),
-            (channel_ga_id, 2),
-        ],
-    );
-
-    // Current shape for C:
-    //        /-------\
-    // ep -- b -- c -- d
-    //        \-- ga
-    assert_channels_list_shape(
-        client_c.channel_store(),
-        cx_c,
-        &[
-            (channel_ep_id, 0),
-            (channel_b_id, 1),
-            (channel_c_id, 2),
-            (channel_d_id, 3),
-            (channel_d_id, 2),
-            (channel_ga_id, 2),
-        ],
-    );
+    // TODO: Restore this test once we have a way to make channel symlinks
+    // client_a
+    //     .channel_store()
+    //     .update(cx_a, |channel_store, cx| {
+    //         channel_store.link_channel(channel_d_id, channel_c_id, cx)
+    //     })
+    //     .await
+    //     .unwrap();
+
+    // // Current shape for A:
+    // //      /------\
+    // // a - b -- c -- d
+    // assert_channels_list_shape(
+    //     client_a.channel_store(),
+    //     cx_a,
+    //     &[
+    //         (channel_a_id, 0),
+    //         (channel_b_id, 1),
+    //         (channel_c_id, 2),
+    //         (channel_d_id, 3),
+    //         (channel_d_id, 2),
+    //     ],
+    // );
+    //
+    // let b_channels = server
+    //     .make_channel_tree(
+    //         &[
+    //             ("channel-mu", None),
+    //             ("channel-gamma", Some("channel-mu")),
+    //             ("channel-epsilon", Some("channel-mu")),
+    //         ],
+    //         (&client_b, cx_b),
+    //     )
+    //     .await;
+    // let channel_mu_id = b_channels[0];
+    // let channel_ga_id = b_channels[1];
+    // let channel_ep_id = b_channels[2];
+
+    // // Current shape for B:
+    // //    /- ep
+    // // mu -- ga
+    // assert_channels_list_shape(
+    //     client_b.channel_store(),
+    //     cx_b,
+    //     &[(channel_mu_id, 0), (channel_ep_id, 1), (channel_ga_id, 1)],
+    // );
+
+    // client_a
+    //     .add_admin_to_channel((&client_b, cx_b), channel_b_id, cx_a)
+    //     .await;
+
+    // // Current shape for B:
+    // //    /- ep
+    // // mu -- ga
+    // //  /---------\
+    // // b  -- c  -- d
+    // assert_channels_list_shape(
+    //     client_b.channel_store(),
+    //     cx_b,
+    //     &[
+    //         // New channels from a
+    //         (channel_b_id, 0),
+    //         (channel_c_id, 1),
+    //         (channel_d_id, 2),
+    //         (channel_d_id, 1),
+    //         // B's old channels
+    //         (channel_mu_id, 0),
+    //         (channel_ep_id, 1),
+    //         (channel_ga_id, 1),
+    //     ],
+    // );
+
+    // client_b
+    //     .add_admin_to_channel((&client_c, cx_c), channel_ep_id, cx_b)
+    //     .await;
+
+    // // Current shape for C:
+    // // - ep
+    // assert_channels_list_shape(client_c.channel_store(), cx_c, &[(channel_ep_id, 0)]);
+
+    // client_b
+    //     .channel_store()
+    //     .update(cx_b, |channel_store, cx| {
+    //         channel_store.link_channel(channel_b_id, channel_ep_id, cx)
+    //     })
+    //     .await
+    //     .unwrap();
+
+    // // Current shape for B:
+    // //              /---------\
+    // //    /- ep -- b  -- c  -- d
+    // // mu -- ga
+    // assert_channels_list_shape(
+    //     client_b.channel_store(),
+    //     cx_b,
+    //     &[
+    //         (channel_mu_id, 0),
+    //         (channel_ep_id, 1),
+    //         (channel_b_id, 2),
+    //         (channel_c_id, 3),
+    //         (channel_d_id, 4),
+    //         (channel_d_id, 3),
+    //         (channel_ga_id, 1),
+    //     ],
+    // );
+
+    // // Current shape for C:
+    // //        /---------\
+    // // ep -- b  -- c  -- d
+    // assert_channels_list_shape(
+    //     client_c.channel_store(),
+    //     cx_c,
+    //     &[
+    //         (channel_ep_id, 0),
+    //         (channel_b_id, 1),
+    //         (channel_c_id, 2),
+    //         (channel_d_id, 3),
+    //         (channel_d_id, 2),
+    //     ],
+    // );
+
+    // client_b
+    //     .channel_store()
+    //     .update(cx_b, |channel_store, cx| {
+    //         channel_store.link_channel(channel_ga_id, channel_b_id, cx)
+    //     })
+    //     .await
+    //     .unwrap();
+
+    // // Current shape for B:
+    // //              /---------\
+    // //    /- ep -- b  -- c  -- d
+    // //   /          \
+    // // mu ---------- ga
+    // assert_channels_list_shape(
+    //     client_b.channel_store(),
+    //     cx_b,
+    //     &[
+    //         (channel_mu_id, 0),
+    //         (channel_ep_id, 1),
+    //         (channel_b_id, 2),
+    //         (channel_c_id, 3),
+    //         (channel_d_id, 4),
+    //         (channel_d_id, 3),
+    //         (channel_ga_id, 3),
+    //         (channel_ga_id, 1),
+    //     ],
+    // );
+
+    // // Current shape for A:
+    // //      /------\
+    // // a - b -- c -- d
+    // //      \-- ga
+    // assert_channels_list_shape(
+    //     client_a.channel_store(),
+    //     cx_a,
+    //     &[
+    //         (channel_a_id, 0),
+    //         (channel_b_id, 1),
+    //         (channel_c_id, 2),
+    //         (channel_d_id, 3),
+    //         (channel_d_id, 2),
+    //         (channel_ga_id, 2),
+    //     ],
+    // );
+
+    // // Current shape for C:
+    // //        /-------\
+    // // ep -- b -- c -- d
+    // //        \-- ga
+    // assert_channels_list_shape(
+    //     client_c.channel_store(),
+    //     cx_c,
+    //     &[
+    //         (channel_ep_id, 0),
+    //         (channel_b_id, 1),
+    //         (channel_c_id, 2),
+    //         (channel_d_id, 3),
+    //         (channel_d_id, 2),
+    //         (channel_ga_id, 2),
+    //     ],
+    // );
 }
 
 #[derive(Debug, PartialEq)]

crates/collab_ui/src/collab_panel.rs 🔗

@@ -120,22 +120,11 @@ struct StartLinkChannelFor {
     parent_id: Option<ChannelId>,
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-struct LinkChannel {
-    to: ChannelId,
-}
-
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
 struct MoveChannel {
     to: ChannelId,
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
-struct UnlinkChannel {
-    channel_id: ChannelId,
-    parent_id: ChannelId,
-}
-
 type DraggedChannel = (Channel, Option<ChannelId>);
 
 actions!(
@@ -147,8 +136,7 @@ actions!(
         CollapseSelectedChannel,
         ExpandSelectedChannel,
         StartMoveChannel,
-        StartLinkChannel,
-        MoveOrLinkToSelected,
+        MoveSelected,
         InsertSpace,
     ]
 );
@@ -166,11 +154,8 @@ impl_actions!(
         JoinChannelCall,
         JoinChannelChat,
         CopyChannelLink,
-        LinkChannel,
         StartMoveChannelFor,
-        StartLinkChannelFor,
         MoveChannel,
-        UnlinkChannel,
         ToggleSelectedIx
     ]
 );
@@ -185,7 +170,7 @@ struct ChannelMoveClipboard {
 #[derive(Debug, Copy, Clone, PartialEq, Eq)]
 enum ClipboardIntent {
     Move,
-    Link,
+    // Link,
 }
 
 const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
@@ -238,18 +223,6 @@ pub fn init(cx: &mut AppContext) {
         },
     );
 
-    cx.add_action(
-        |panel: &mut CollabPanel,
-         action: &StartLinkChannelFor,
-         _: &mut ViewContext<CollabPanel>| {
-            panel.channel_clipboard = Some(ChannelMoveClipboard {
-                channel_id: action.channel_id,
-                parent_id: action.parent_id,
-                intent: ClipboardIntent::Link,
-            })
-        },
-    );
-
     cx.add_action(
         |panel: &mut CollabPanel, _: &StartMoveChannel, _: &mut ViewContext<CollabPanel>| {
             if let Some((_, path)) = panel.selected_channel() {
@@ -263,86 +236,51 @@ pub fn init(cx: &mut AppContext) {
     );
 
     cx.add_action(
-        |panel: &mut CollabPanel, _: &StartLinkChannel, _: &mut ViewContext<CollabPanel>| {
-            if let Some((_, path)) = panel.selected_channel() {
-                panel.channel_clipboard = Some(ChannelMoveClipboard {
-                    channel_id: path.channel_id(),
-                    parent_id: path.parent_id(),
-                    intent: ClipboardIntent::Link,
-                })
-            }
-        },
-    );
-
-    cx.add_action(
-        |panel: &mut CollabPanel, _: &MoveOrLinkToSelected, cx: &mut ViewContext<CollabPanel>| {
+        |panel: &mut CollabPanel, _: &MoveSelected, cx: &mut ViewContext<CollabPanel>| {
             let clipboard = panel.channel_clipboard.take();
             if let Some(((selected_channel, _), clipboard)) =
                 panel.selected_channel().zip(clipboard)
             {
                 match clipboard.intent {
-                    ClipboardIntent::Move if clipboard.parent_id.is_some() => {
-                        let parent_id = clipboard.parent_id.unwrap();
-                        panel.channel_store.update(cx, |channel_store, cx| {
-                            channel_store
-                                .move_channel(
-                                    clipboard.channel_id,
-                                    parent_id,
-                                    selected_channel.id,
-                                    cx,
-                                )
-                                .detach_and_log_err(cx)
-                        })
-                    }
-                    _ => panel.channel_store.update(cx, |channel_store, cx| {
-                        channel_store
-                            .link_channel(clipboard.channel_id, selected_channel.id, cx)
-                            .detach_and_log_err(cx)
+                    ClipboardIntent::Move => panel.channel_store.update(cx, |channel_store, cx| {
+                        match clipboard.parent_id {
+                            Some(parent_id) => channel_store.move_channel(
+                                clipboard.channel_id,
+                                parent_id,
+                                selected_channel.id,
+                                cx,
+                            ),
+                            None => channel_store.link_channel(
+                                clipboard.channel_id,
+                                selected_channel.id,
+                                cx,
+                            ),
+                        }
+                        .detach_and_log_err(cx)
                     }),
                 }
             }
         },
     );
 
-    cx.add_action(
-        |panel: &mut CollabPanel, action: &LinkChannel, cx: &mut ViewContext<CollabPanel>| {
-            if let Some(clipboard) = panel.channel_clipboard.take() {
-                panel.channel_store.update(cx, |channel_store, cx| {
-                    channel_store
-                        .link_channel(clipboard.channel_id, action.to, cx)
-                        .detach_and_log_err(cx)
-                })
-            }
-        },
-    );
-
     cx.add_action(
         |panel: &mut CollabPanel, action: &MoveChannel, cx: &mut ViewContext<CollabPanel>| {
             if let Some(clipboard) = panel.channel_clipboard.take() {
                 panel.channel_store.update(cx, |channel_store, cx| {
-                    if let Some(parent) = clipboard.parent_id {
-                        channel_store
-                            .move_channel(clipboard.channel_id, parent, action.to, cx)
-                            .detach_and_log_err(cx)
-                    } else {
-                        channel_store
-                            .link_channel(clipboard.channel_id, action.to, cx)
-                            .detach_and_log_err(cx)
+                    match clipboard.parent_id {
+                        Some(parent_id) => channel_store.move_channel(
+                            clipboard.channel_id,
+                            parent_id,
+                            action.to,
+                            cx,
+                        ),
+                        None => channel_store.link_channel(clipboard.channel_id, action.to, cx),
                     }
+                    .detach_and_log_err(cx)
                 })
             }
         },
     );
-
-    cx.add_action(
-        |panel: &mut CollabPanel, action: &UnlinkChannel, cx: &mut ViewContext<CollabPanel>| {
-            panel.channel_store.update(cx, |channel_store, cx| {
-                channel_store
-                    .unlink_channel(action.channel_id, action.parent_id, cx)
-                    .detach_and_log_err(cx)
-            })
-        },
-    );
 }
 
 #[derive(Debug)]
@@ -2235,33 +2173,23 @@ impl CollabPanel {
                 this.deploy_channel_context_menu(Some(e.position), &path, ix, cx);
             }
         })
-        .on_up(MouseButton::Left, move |e, this, cx| {
+        .on_up(MouseButton::Left, move |_, this, cx| {
             if let Some((_, dragged_channel)) = cx
                 .global::<DragAndDrop<Workspace>>()
                 .currently_dragged::<DraggedChannel>(cx.window())
             {
-                if e.modifiers.alt {
-                    this.channel_store.update(cx, |channel_store, cx| {
-                        channel_store
-                            .link_channel(dragged_channel.0.id, channel_id, cx)
-                            .detach_and_log_err(cx)
-                    })
-                } else {
-                    this.channel_store.update(cx, |channel_store, cx| {
-                        match dragged_channel.1 {
-                            Some(parent_id) => channel_store.move_channel(
-                                dragged_channel.0.id,
-                                parent_id,
-                                channel_id,
-                                cx,
-                            ),
-                            None => {
-                                channel_store.link_channel(dragged_channel.0.id, channel_id, cx)
-                            }
-                        }
-                        .detach_and_log_err(cx)
-                    })
-                }
+                this.channel_store.update(cx, |channel_store, cx| {
+                    match dragged_channel.1 {
+                        Some(parent_id) => channel_store.move_channel(
+                            dragged_channel.0.id,
+                            parent_id,
+                            channel_id,
+                            cx,
+                        ),
+                        None => channel_store.link_channel(dragged_channel.0.id, channel_id, cx),
+                    }
+                    .detach_and_log_err(cx)
+                })
             }
         })
         .on_move({
@@ -2288,18 +2216,10 @@ impl CollabPanel {
         })
         .as_draggable(
             (channel.clone(), path.parent_id()),
-            move |modifiers, (channel, _), cx: &mut ViewContext<Workspace>| {
+            move |_, (channel, _), cx: &mut ViewContext<Workspace>| {
                 let theme = &theme::current(cx).collab_panel;
 
                 Flex::<Workspace>::row()
-                    .with_children(modifiers.alt.then(|| {
-                        Svg::new("icons/plus.svg")
-                            .with_color(theme.channel_hash.color)
-                            .constrained()
-                            .with_width(theme.channel_hash.width)
-                            .aligned()
-                            .left()
-                    }))
                     .with_child(
                         Svg::new("icons/hash.svg")
                             .with_color(theme.channel_hash.color)
@@ -2743,19 +2663,6 @@ impl CollabPanel {
                         },
                     ),
                     ContextMenuItem::Separator,
-                ]);
-
-                if let Some(parent_id) = parent_id {
-                    items.push(ContextMenuItem::action(
-                        "Unlink from parent",
-                        UnlinkChannel {
-                            channel_id: path.channel_id(),
-                            parent_id,
-                        },
-                    ));
-                }
-
-                items.extend([
                     ContextMenuItem::action(
                         "Move this channel",
                         StartMoveChannelFor {
@@ -2763,13 +2670,6 @@ impl CollabPanel {
                             parent_id,
                         },
                     ),
-                    ContextMenuItem::action(
-                        "Link this channel",
-                        StartLinkChannelFor {
-                            channel_id: path.channel_id(),
-                            parent_id,
-                        },
-                    ),
                 ]);
 
                 if let Some(channel_name) = channel_name {
@@ -2780,12 +2680,6 @@ impl CollabPanel {
                             to: path.channel_id(),
                         },
                     ));
-                    items.push(ContextMenuItem::action(
-                        format!("Link '#{}' here", channel_name),
-                        LinkChannel {
-                            to: path.channel_id(),
-                        },
-                    ));
                 }
 
                 items.extend([

crates/rpc/proto/zed.proto 🔗

@@ -970,10 +970,16 @@ message UpdateChannels {
     repeated Channel channel_invitations = 5;
     repeated uint64 remove_channel_invitations = 6;
     repeated ChannelParticipants channel_participants = 7;
+    //repeated ChannelRoles channel_roles = 8;
     repeated UnseenChannelMessage unseen_channel_messages = 9;
     repeated UnseenChannelBufferChange unseen_channel_buffer_changes = 10;
 }
 
+//message ChannelRoles {
+//    ChannelRole role = 1;
+//    uint64 channel_id = 2;
+//}
+
 message UnseenChannelMessage {
     uint64 channel_id = 1;
     uint64 message_id = 2;