Uncomment enough to register the panel

Conrad Irwin created

Change summary

crates/collab_ui2/src/collab_panel.rs | 6210 ++++++++++++++--------------
crates/collab_ui2/src/collab_ui.rs    |    3 
crates/zed2/src/zed2.rs               |   12 
3 files changed, 3,119 insertions(+), 3,106 deletions(-)

Detailed changes

crates/collab_ui2/src/collab_panel.rs 🔗

@@ -118,3123 +118,3135 @@
 //     to: ChannelId,
 // }
 
-// actions!(
-//     collab_panel,
-//     [
-//         ToggleFocus,
-//         Remove,
-//         Secondary,
-//         CollapseSelectedChannel,
-//         ExpandSelectedChannel,
-//         StartMoveChannel,
-//         MoveSelected,
-//         InsertSpace,
-//     ]
-// );
-
-// impl_actions!(
-//     collab_panel,
-//     [
-//         RemoveChannel,
-//         NewChannel,
-//         InviteMembers,
-//         ManageMembers,
-//         RenameChannel,
-//         ToggleCollapse,
-//         OpenChannelNotes,
-//         JoinChannelCall,
-//         JoinChannelChat,
-//         CopyChannelLink,
-//         StartMoveChannelFor,
-//         MoveChannel,
-//         ToggleSelectedIx
-//     ]
-// );
-
-// #[derive(Debug, Copy, Clone, PartialEq, Eq)]
-// struct ChannelMoveClipboard {
-//     channel_id: ChannelId,
-// }
-
-// const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
-
-// pub fn init(cx: &mut AppContext) {
-//     settings::register::<panel_settings::CollaborationPanelSettings>(cx);
-//     contact_finder::init(cx);
-//     channel_modal::init(cx);
-//     channel_view::init(cx);
-
-//     cx.add_action(CollabPanel::cancel);
-//     cx.add_action(CollabPanel::select_next);
-//     cx.add_action(CollabPanel::select_prev);
-//     cx.add_action(CollabPanel::confirm);
-//     cx.add_action(CollabPanel::insert_space);
-//     cx.add_action(CollabPanel::remove);
-//     cx.add_action(CollabPanel::remove_selected_channel);
-//     cx.add_action(CollabPanel::show_inline_context_menu);
-//     cx.add_action(CollabPanel::new_subchannel);
-//     cx.add_action(CollabPanel::invite_members);
-//     cx.add_action(CollabPanel::manage_members);
-//     cx.add_action(CollabPanel::rename_selected_channel);
-//     cx.add_action(CollabPanel::rename_channel);
-//     cx.add_action(CollabPanel::toggle_channel_collapsed_action);
-//     cx.add_action(CollabPanel::collapse_selected_channel);
-//     cx.add_action(CollabPanel::expand_selected_channel);
-//     cx.add_action(CollabPanel::open_channel_notes);
-//     cx.add_action(CollabPanel::join_channel_chat);
-//     cx.add_action(CollabPanel::copy_channel_link);
-
-//     cx.add_action(
-//         |panel: &mut CollabPanel, action: &ToggleSelectedIx, cx: &mut ViewContext<CollabPanel>| {
-//             if panel.selection.take() != Some(action.ix) {
-//                 panel.selection = Some(action.ix)
-//             }
-
-//             cx.notify();
-//         },
-//     );
-
-//     cx.add_action(
-//         |panel: &mut CollabPanel,
-//          action: &StartMoveChannelFor,
-//          _: &mut ViewContext<CollabPanel>| {
-//             panel.channel_clipboard = Some(ChannelMoveClipboard {
-//                 channel_id: action.channel_id,
-//             });
-//         },
-//     );
-
-//     cx.add_action(
-//         |panel: &mut CollabPanel, _: &StartMoveChannel, _: &mut ViewContext<CollabPanel>| {
-//             if let Some(channel) = panel.selected_channel() {
-//                 panel.channel_clipboard = Some(ChannelMoveClipboard {
-//                     channel_id: channel.id,
-//                 })
-//             }
-//         },
-//     );
-
-//     cx.add_action(
-//         |panel: &mut CollabPanel, _: &MoveSelected, cx: &mut ViewContext<CollabPanel>| {
-//             let Some(clipboard) = panel.channel_clipboard.take() else {
-//                 return;
-//             };
-//             let Some(selected_channel) = panel.selected_channel() else {
-//                 return;
-//             };
-
-//             panel
-//                 .channel_store
-//                 .update(cx, |channel_store, cx| {
-//                     channel_store.move_channel(clipboard.channel_id, Some(selected_channel.id), 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| {
-//                     channel_store
-//                         .move_channel(clipboard.channel_id, Some(action.to), cx)
-//                         .detach_and_log_err(cx)
-//                 })
-//             }
-//         },
-//     );
-// }
-
-// #[derive(Debug)]
-// pub enum ChannelEditingState {
-//     Create {
-//         location: Option<ChannelId>,
-//         pending_name: Option<String>,
-//     },
-//     Rename {
-//         location: ChannelId,
-//         pending_name: Option<String>,
-//     },
-// }
-
-// impl ChannelEditingState {
-//     fn pending_name(&self) -> Option<&str> {
-//         match self {
-//             ChannelEditingState::Create { pending_name, .. } => pending_name.as_deref(),
-//             ChannelEditingState::Rename { pending_name, .. } => pending_name.as_deref(),
-//         }
-//     }
-// }
-
-// pub struct CollabPanel {
-//     width: Option<f32>,
-//     fs: Arc<dyn Fs>,
-//     has_focus: bool,
-//     channel_clipboard: Option<ChannelMoveClipboard>,
-//     pending_serialization: Task<Option<()>>,
-//     context_menu: ViewHandle<ContextMenu>,
-//     filter_editor: ViewHandle<Editor>,
-//     channel_name_editor: ViewHandle<Editor>,
-//     channel_editing_state: Option<ChannelEditingState>,
-//     entries: Vec<ListEntry>,
-//     selection: Option<usize>,
-//     user_store: ModelHandle<UserStore>,
-//     client: Arc<Client>,
-//     channel_store: ModelHandle<ChannelStore>,
-//     project: ModelHandle<Project>,
-//     match_candidates: Vec<StringMatchCandidate>,
-//     list_state: ListState<Self>,
-//     subscriptions: Vec<Subscription>,
-//     collapsed_sections: Vec<Section>,
-//     collapsed_channels: Vec<ChannelId>,
-//     drag_target_channel: ChannelDragTarget,
-//     workspace: WeakViewHandle<Workspace>,
-//     context_menu_on_selected: bool,
-// }
-
-// #[derive(PartialEq, Eq)]
-// enum ChannelDragTarget {
-//     None,
-//     Root,
-//     Channel(ChannelId),
-// }
-
-// #[derive(Serialize, Deserialize)]
-// struct SerializedCollabPanel {
-//     width: Option<f32>,
-//     collapsed_channels: Option<Vec<ChannelId>>,
-// }
-
-// #[derive(Debug)]
-// pub enum Event {
-//     DockPositionChanged,
-//     Focus,
-//     Dismissed,
-// }
-
-// #[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
-// enum Section {
-//     ActiveCall,
-//     Channels,
-//     ChannelInvites,
-//     ContactRequests,
-//     Contacts,
-//     Online,
-//     Offline,
-// }
-
-// #[derive(Clone, Debug)]
-// enum ListEntry {
-//     Header(Section),
-//     CallParticipant {
-//         user: Arc<User>,
-//         peer_id: Option<PeerId>,
-//         is_pending: bool,
-//     },
-//     ParticipantProject {
-//         project_id: u64,
-//         worktree_root_names: Vec<String>,
-//         host_user_id: u64,
-//         is_last: bool,
-//     },
-//     ParticipantScreen {
-//         peer_id: Option<PeerId>,
-//         is_last: bool,
-//     },
-//     IncomingRequest(Arc<User>),
-//     OutgoingRequest(Arc<User>),
-//     ChannelInvite(Arc<Channel>),
-//     Channel {
-//         channel: Arc<Channel>,
-//         depth: usize,
-//         has_children: bool,
-//     },
-//     ChannelNotes {
-//         channel_id: ChannelId,
-//     },
-//     ChannelChat {
-//         channel_id: ChannelId,
-//     },
-//     ChannelEditor {
-//         depth: usize,
-//     },
-//     Contact {
-//         contact: Arc<Contact>,
-//         calling: bool,
-//     },
-//     ContactPlaceholder,
-// }
-
-// impl Entity for CollabPanel {
-//     type Event = Event;
-// }
-
-// impl CollabPanel {
-//     pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> ViewHandle<Self> {
-//         cx.add_view::<Self, _>(|cx| {
-//             let view_id = cx.view_id();
-
-//             let filter_editor = cx.add_view(|cx| {
-//                 let mut editor = Editor::single_line(
-//                     Some(Arc::new(|theme| {
-//                         theme.collab_panel.user_query_editor.clone()
-//                     })),
-//                     cx,
-//                 );
-//                 editor.set_placeholder_text("Filter channels, contacts", cx);
-//                 editor
-//             });
-
-//             cx.subscribe(&filter_editor, |this, _, event, cx| {
-//                 if let editor::Event::BufferEdited = event {
-//                     let query = this.filter_editor.read(cx).text(cx);
-//                     if !query.is_empty() {
-//                         this.selection.take();
-//                     }
-//                     this.update_entries(true, cx);
-//                     if !query.is_empty() {
-//                         this.selection = this
-//                             .entries
-//                             .iter()
-//                             .position(|entry| !matches!(entry, ListEntry::Header(_)));
-//                     }
-//                 } else if let editor::Event::Blurred = event {
-//                     let query = this.filter_editor.read(cx).text(cx);
-//                     if query.is_empty() {
-//                         this.selection.take();
-//                         this.update_entries(true, cx);
-//                     }
-//                 }
-//             })
-//             .detach();
-
-//             let channel_name_editor = cx.add_view(|cx| {
-//                 Editor::single_line(
-//                     Some(Arc::new(|theme| {
-//                         theme.collab_panel.user_query_editor.clone()
-//                     })),
-//                     cx,
-//                 )
-//             });
-
-//             cx.subscribe(&channel_name_editor, |this, _, event, cx| {
-//                 if let editor::Event::Blurred = event {
-//                     if let Some(state) = &this.channel_editing_state {
-//                         if state.pending_name().is_some() {
-//                             return;
-//                         }
-//                     }
-//                     this.take_editing_state(cx);
-//                     this.update_entries(false, cx);
-//                     cx.notify();
-//                 }
-//             })
-//             .detach();
-
-//             let list_state =
-//                 ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
-//                     let theme = theme::current(cx).clone();
-//                     let is_selected = this.selection == Some(ix);
-//                     let current_project_id = this.project.read(cx).remote_id();
-
-//                     match &this.entries[ix] {
-//                         ListEntry::Header(section) => {
-//                             let is_collapsed = this.collapsed_sections.contains(section);
-//                             this.render_header(*section, &theme, is_selected, is_collapsed, cx)
-//                         }
-//                         ListEntry::CallParticipant {
-//                             user,
-//                             peer_id,
-//                             is_pending,
-//                         } => Self::render_call_participant(
-//                             user,
-//                             *peer_id,
-//                             this.user_store.clone(),
-//                             *is_pending,
-//                             is_selected,
-//                             &theme,
-//                             cx,
-//                         ),
-//                         ListEntry::ParticipantProject {
-//                             project_id,
-//                             worktree_root_names,
-//                             host_user_id,
-//                             is_last,
-//                         } => Self::render_participant_project(
-//                             *project_id,
-//                             worktree_root_names,
-//                             *host_user_id,
-//                             Some(*project_id) == current_project_id,
-//                             *is_last,
-//                             is_selected,
-//                             &theme,
-//                             cx,
-//                         ),
-//                         ListEntry::ParticipantScreen { peer_id, is_last } => {
-//                             Self::render_participant_screen(
-//                                 *peer_id,
-//                                 *is_last,
-//                                 is_selected,
-//                                 &theme.collab_panel,
-//                                 cx,
-//                             )
-//                         }
-//                         ListEntry::Channel {
-//                             channel,
-//                             depth,
-//                             has_children,
-//                         } => {
-//                             let channel_row = this.render_channel(
-//                                 &*channel,
-//                                 *depth,
-//                                 &theme,
-//                                 is_selected,
-//                                 *has_children,
-//                                 ix,
-//                                 cx,
-//                             );
-
-//                             if is_selected && this.context_menu_on_selected {
-//                                 Stack::new()
-//                                     .with_child(channel_row)
-//                                     .with_child(
-//                                         ChildView::new(&this.context_menu, cx)
-//                                             .aligned()
-//                                             .bottom()
-//                                             .right(),
-//                                     )
-//                                     .into_any()
-//                             } else {
-//                                 return channel_row;
-//                             }
-//                         }
-//                         ListEntry::ChannelNotes { channel_id } => this.render_channel_notes(
-//                             *channel_id,
-//                             &theme.collab_panel,
-//                             is_selected,
-//                             ix,
-//                             cx,
-//                         ),
-//                         ListEntry::ChannelChat { channel_id } => this.render_channel_chat(
-//                             *channel_id,
-//                             &theme.collab_panel,
-//                             is_selected,
-//                             ix,
-//                             cx,
-//                         ),
-//                         ListEntry::ChannelInvite(channel) => Self::render_channel_invite(
-//                             channel.clone(),
-//                             this.channel_store.clone(),
-//                             &theme.collab_panel,
-//                             is_selected,
-//                             cx,
-//                         ),
-//                         ListEntry::IncomingRequest(user) => Self::render_contact_request(
-//                             user.clone(),
-//                             this.user_store.clone(),
-//                             &theme.collab_panel,
-//                             true,
-//                             is_selected,
-//                             cx,
-//                         ),
-//                         ListEntry::OutgoingRequest(user) => Self::render_contact_request(
-//                             user.clone(),
-//                             this.user_store.clone(),
-//                             &theme.collab_panel,
-//                             false,
-//                             is_selected,
-//                             cx,
-//                         ),
-//                         ListEntry::Contact { contact, calling } => Self::render_contact(
-//                             contact,
-//                             *calling,
-//                             &this.project,
-//                             &theme,
-//                             is_selected,
-//                             cx,
-//                         ),
-//                         ListEntry::ChannelEditor { depth } => {
-//                             this.render_channel_editor(&theme, *depth, cx)
-//                         }
-//                         ListEntry::ContactPlaceholder => {
-//                             this.render_contact_placeholder(&theme.collab_panel, is_selected, cx)
-//                         }
-//                     }
-//                 });
-
-//             let mut this = Self {
-//                 width: None,
-//                 has_focus: false,
-//                 channel_clipboard: None,
-//                 fs: workspace.app_state().fs.clone(),
-//                 pending_serialization: Task::ready(None),
-//                 context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
-//                 channel_name_editor,
-//                 filter_editor,
-//                 entries: Vec::default(),
-//                 channel_editing_state: None,
-//                 selection: None,
-//                 user_store: workspace.user_store().clone(),
-//                 channel_store: ChannelStore::global(cx),
-//                 project: workspace.project().clone(),
-//                 subscriptions: Vec::default(),
-//                 match_candidates: Vec::default(),
-//                 collapsed_sections: vec![Section::Offline],
-//                 collapsed_channels: Vec::default(),
-//                 workspace: workspace.weak_handle(),
-//                 client: workspace.app_state().client.clone(),
-//                 context_menu_on_selected: true,
-//                 drag_target_channel: ChannelDragTarget::None,
-//                 list_state,
-//             };
-
-//             this.update_entries(false, cx);
-
-//             // Update the dock position when the setting changes.
-//             let mut old_dock_position = this.position(cx);
-//             this.subscriptions
-//                 .push(
-//                     cx.observe_global::<SettingsStore, _>(move |this: &mut Self, cx| {
-//                         let new_dock_position = this.position(cx);
-//                         if new_dock_position != old_dock_position {
-//                             old_dock_position = new_dock_position;
-//                             cx.emit(Event::DockPositionChanged);
-//                         }
-//                         cx.notify();
-//                     }),
-//                 );
-
-//             let active_call = ActiveCall::global(cx);
-//             this.subscriptions
-//                 .push(cx.observe(&this.user_store, |this, _, cx| {
-//                     this.update_entries(true, cx)
-//                 }));
-//             this.subscriptions
-//                 .push(cx.observe(&this.channel_store, |this, _, cx| {
-//                     this.update_entries(true, cx)
-//                 }));
-//             this.subscriptions
-//                 .push(cx.observe(&active_call, |this, _, cx| this.update_entries(true, cx)));
-//             this.subscriptions
-//                 .push(cx.observe_flag::<ChannelsAlpha, _>(move |_, this, cx| {
-//                     this.update_entries(true, cx)
-//                 }));
-//             this.subscriptions.push(cx.subscribe(
-//                 &this.channel_store,
-//                 |this, _channel_store, e, cx| match e {
-//                     ChannelEvent::ChannelCreated(channel_id)
-//                     | ChannelEvent::ChannelRenamed(channel_id) => {
-//                         if this.take_editing_state(cx) {
-//                             this.update_entries(false, cx);
-//                             this.selection = this.entries.iter().position(|entry| {
-//                                 if let ListEntry::Channel { channel, .. } = entry {
-//                                     channel.id == *channel_id
-//                                 } else {
-//                                     false
-//                                 }
-//                             });
-//                         }
-//                     }
-//                 },
-//             ));
-
-//             this
-//         })
-//     }
-
-//     pub fn load(
-//         workspace: WeakViewHandle<Workspace>,
-//         cx: AsyncAppContext,
-//     ) -> Task<Result<ViewHandle<Self>>> {
-//         cx.spawn(|mut cx| async move {
-//             let serialized_panel = if let Some(panel) = cx
-//                 .background()
-//                 .spawn(async move { KEY_VALUE_STORE.read_kvp(COLLABORATION_PANEL_KEY) })
-//                 .await
-//                 .log_err()
-//                 .flatten()
-//             {
-//                 match serde_json::from_str::<SerializedCollabPanel>(&panel) {
-//                     Ok(panel) => Some(panel),
-//                     Err(err) => {
-//                         log::error!("Failed to deserialize collaboration panel: {}", err);
-//                         None
-//                     }
-//                 }
-//             } else {
-//                 None
-//             };
-
-//             workspace.update(&mut cx, |workspace, cx| {
-//                 let panel = CollabPanel::new(workspace, cx);
-//                 if let Some(serialized_panel) = serialized_panel {
-//                     panel.update(cx, |panel, cx| {
-//                         panel.width = serialized_panel.width;
-//                         panel.collapsed_channels = serialized_panel
-//                             .collapsed_channels
-//                             .unwrap_or_else(|| Vec::new());
-//                         cx.notify();
-//                     });
-//                 }
-//                 panel
-//             })
-//         })
-//     }
-
-//     fn serialize(&mut self, cx: &mut ViewContext<Self>) {
-//         let width = self.width;
-//         let collapsed_channels = self.collapsed_channels.clone();
-//         self.pending_serialization = cx.background().spawn(
-//             async move {
-//                 KEY_VALUE_STORE
-//                     .write_kvp(
-//                         COLLABORATION_PANEL_KEY.into(),
-//                         serde_json::to_string(&SerializedCollabPanel {
-//                             width,
-//                             collapsed_channels: Some(collapsed_channels),
-//                         })?,
-//                     )
-//                     .await?;
-//                 anyhow::Ok(())
-//             }
-//             .log_err(),
-//         );
-//     }
-
-//     fn update_entries(&mut self, select_same_item: bool, cx: &mut ViewContext<Self>) {
-//         let channel_store = self.channel_store.read(cx);
-//         let user_store = self.user_store.read(cx);
-//         let query = self.filter_editor.read(cx).text(cx);
-//         let executor = cx.background().clone();
-
-//         let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
-//         let old_entries = mem::take(&mut self.entries);
-//         let mut scroll_to_top = false;
-
-//         if let Some(room) = ActiveCall::global(cx).read(cx).room() {
-//             self.entries.push(ListEntry::Header(Section::ActiveCall));
-//             if !old_entries
-//                 .iter()
-//                 .any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall)))
-//             {
-//                 scroll_to_top = true;
-//             }
-
-//             if !self.collapsed_sections.contains(&Section::ActiveCall) {
-//                 let room = room.read(cx);
-
-//                 if let Some(channel_id) = room.channel_id() {
-//                     self.entries.push(ListEntry::ChannelNotes { channel_id });
-//                     self.entries.push(ListEntry::ChannelChat { channel_id })
-//                 }
-
-//                 // Populate the active user.
-//                 if let Some(user) = user_store.current_user() {
-//                     self.match_candidates.clear();
-//                     self.match_candidates.push(StringMatchCandidate {
-//                         id: 0,
-//                         string: user.github_login.clone(),
-//                         char_bag: user.github_login.chars().collect(),
-//                     });
-//                     let matches = executor.block(match_strings(
-//                         &self.match_candidates,
-//                         &query,
-//                         true,
-//                         usize::MAX,
-//                         &Default::default(),
-//                         executor.clone(),
-//                     ));
-//                     if !matches.is_empty() {
-//                         let user_id = user.id;
-//                         self.entries.push(ListEntry::CallParticipant {
-//                             user,
-//                             peer_id: None,
-//                             is_pending: false,
-//                         });
-//                         let mut projects = room.local_participant().projects.iter().peekable();
-//                         while let Some(project) = projects.next() {
-//                             self.entries.push(ListEntry::ParticipantProject {
-//                                 project_id: project.id,
-//                                 worktree_root_names: project.worktree_root_names.clone(),
-//                                 host_user_id: user_id,
-//                                 is_last: projects.peek().is_none() && !room.is_screen_sharing(),
-//                             });
-//                         }
-//                         if room.is_screen_sharing() {
-//                             self.entries.push(ListEntry::ParticipantScreen {
-//                                 peer_id: None,
-//                                 is_last: true,
-//                             });
-//                         }
-//                     }
-//                 }
-
-//                 // Populate remote participants.
-//                 self.match_candidates.clear();
-//                 self.match_candidates
-//                     .extend(room.remote_participants().iter().map(|(_, participant)| {
-//                         StringMatchCandidate {
-//                             id: participant.user.id as usize,
-//                             string: participant.user.github_login.clone(),
-//                             char_bag: participant.user.github_login.chars().collect(),
-//                         }
-//                     }));
-//                 let matches = executor.block(match_strings(
-//                     &self.match_candidates,
-//                     &query,
-//                     true,
-//                     usize::MAX,
-//                     &Default::default(),
-//                     executor.clone(),
-//                 ));
-//                 for mat in matches {
-//                     let user_id = mat.candidate_id as u64;
-//                     let participant = &room.remote_participants()[&user_id];
-//                     self.entries.push(ListEntry::CallParticipant {
-//                         user: participant.user.clone(),
-//                         peer_id: Some(participant.peer_id),
-//                         is_pending: false,
-//                     });
-//                     let mut projects = participant.projects.iter().peekable();
-//                     while let Some(project) = projects.next() {
-//                         self.entries.push(ListEntry::ParticipantProject {
-//                             project_id: project.id,
-//                             worktree_root_names: project.worktree_root_names.clone(),
-//                             host_user_id: participant.user.id,
-//                             is_last: projects.peek().is_none()
-//                                 && participant.video_tracks.is_empty(),
-//                         });
-//                     }
-//                     if !participant.video_tracks.is_empty() {
-//                         self.entries.push(ListEntry::ParticipantScreen {
-//                             peer_id: Some(participant.peer_id),
-//                             is_last: true,
-//                         });
-//                     }
-//                 }
-
-//                 // Populate pending participants.
-//                 self.match_candidates.clear();
-//                 self.match_candidates
-//                     .extend(room.pending_participants().iter().enumerate().map(
-//                         |(id, participant)| StringMatchCandidate {
-//                             id,
-//                             string: participant.github_login.clone(),
-//                             char_bag: participant.github_login.chars().collect(),
-//                         },
-//                     ));
-//                 let matches = executor.block(match_strings(
-//                     &self.match_candidates,
-//                     &query,
-//                     true,
-//                     usize::MAX,
-//                     &Default::default(),
-//                     executor.clone(),
-//                 ));
-//                 self.entries
-//                     .extend(matches.iter().map(|mat| ListEntry::CallParticipant {
-//                         user: room.pending_participants()[mat.candidate_id].clone(),
-//                         peer_id: None,
-//                         is_pending: true,
-//                     }));
-//             }
-//         }
-
-//         let mut request_entries = Vec::new();
-
-//         if cx.has_flag::<ChannelsAlpha>() {
-//             self.entries.push(ListEntry::Header(Section::Channels));
-
-//             if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() {
-//                 self.match_candidates.clear();
-//                 self.match_candidates
-//                     .extend(channel_store.ordered_channels().enumerate().map(
-//                         |(ix, (_, channel))| StringMatchCandidate {
-//                             id: ix,
-//                             string: channel.name.clone(),
-//                             char_bag: channel.name.chars().collect(),
-//                         },
-//                     ));
-//                 let matches = executor.block(match_strings(
-//                     &self.match_candidates,
-//                     &query,
-//                     true,
-//                     usize::MAX,
-//                     &Default::default(),
-//                     executor.clone(),
-//                 ));
-//                 if let Some(state) = &self.channel_editing_state {
-//                     if matches!(state, ChannelEditingState::Create { location: None, .. }) {
-//                         self.entries.push(ListEntry::ChannelEditor { depth: 0 });
-//                     }
-//                 }
-//                 let mut collapse_depth = None;
-//                 for mat in matches {
-//                     let channel = channel_store.channel_at_index(mat.candidate_id).unwrap();
-//                     let depth = channel.parent_path.len();
-
-//                     if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) {
-//                         collapse_depth = Some(depth);
-//                     } else if let Some(collapsed_depth) = collapse_depth {
-//                         if depth > collapsed_depth {
-//                             continue;
-//                         }
-//                         if self.is_channel_collapsed(channel.id) {
-//                             collapse_depth = Some(depth);
-//                         } else {
-//                             collapse_depth = None;
-//                         }
-//                     }
-
-//                     let has_children = channel_store
-//                         .channel_at_index(mat.candidate_id + 1)
-//                         .map_or(false, |next_channel| {
-//                             next_channel.parent_path.ends_with(&[channel.id])
-//                         });
-
-//                     match &self.channel_editing_state {
-//                         Some(ChannelEditingState::Create {
-//                             location: parent_id,
-//                             ..
-//                         }) if *parent_id == Some(channel.id) => {
-//                             self.entries.push(ListEntry::Channel {
-//                                 channel: channel.clone(),
-//                                 depth,
-//                                 has_children: false,
-//                             });
-//                             self.entries
-//                                 .push(ListEntry::ChannelEditor { depth: depth + 1 });
-//                         }
-//                         Some(ChannelEditingState::Rename {
-//                             location: parent_id,
-//                             ..
-//                         }) if parent_id == &channel.id => {
-//                             self.entries.push(ListEntry::ChannelEditor { depth });
-//                         }
-//                         _ => {
-//                             self.entries.push(ListEntry::Channel {
-//                                 channel: channel.clone(),
-//                                 depth,
-//                                 has_children,
-//                             });
-//                         }
-//                     }
-//                 }
-//             }
-
-//             let channel_invites = channel_store.channel_invitations();
-//             if !channel_invites.is_empty() {
-//                 self.match_candidates.clear();
-//                 self.match_candidates
-//                     .extend(channel_invites.iter().enumerate().map(|(ix, channel)| {
-//                         StringMatchCandidate {
-//                             id: ix,
-//                             string: channel.name.clone(),
-//                             char_bag: channel.name.chars().collect(),
-//                         }
-//                     }));
-//                 let matches = executor.block(match_strings(
-//                     &self.match_candidates,
-//                     &query,
-//                     true,
-//                     usize::MAX,
-//                     &Default::default(),
-//                     executor.clone(),
-//                 ));
-//                 request_entries.extend(matches.iter().map(|mat| {
-//                     ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone())
-//                 }));
-
-//                 if !request_entries.is_empty() {
-//                     self.entries
-//                         .push(ListEntry::Header(Section::ChannelInvites));
-//                     if !self.collapsed_sections.contains(&Section::ChannelInvites) {
-//                         self.entries.append(&mut request_entries);
-//                     }
-//                 }
-//             }
-//         }
-
-//         self.entries.push(ListEntry::Header(Section::Contacts));
-
-//         request_entries.clear();
-//         let incoming = user_store.incoming_contact_requests();
-//         if !incoming.is_empty() {
-//             self.match_candidates.clear();
-//             self.match_candidates
-//                 .extend(
-//                     incoming
-//                         .iter()
-//                         .enumerate()
-//                         .map(|(ix, user)| StringMatchCandidate {
-//                             id: ix,
-//                             string: user.github_login.clone(),
-//                             char_bag: user.github_login.chars().collect(),
-//                         }),
-//                 );
-//             let matches = executor.block(match_strings(
-//                 &self.match_candidates,
-//                 &query,
-//                 true,
-//                 usize::MAX,
-//                 &Default::default(),
-//                 executor.clone(),
-//             ));
-//             request_entries.extend(
-//                 matches
-//                     .iter()
-//                     .map(|mat| ListEntry::IncomingRequest(incoming[mat.candidate_id].clone())),
-//             );
-//         }
-
-//         let outgoing = user_store.outgoing_contact_requests();
-//         if !outgoing.is_empty() {
-//             self.match_candidates.clear();
-//             self.match_candidates
-//                 .extend(
-//                     outgoing
-//                         .iter()
-//                         .enumerate()
-//                         .map(|(ix, user)| StringMatchCandidate {
-//                             id: ix,
-//                             string: user.github_login.clone(),
-//                             char_bag: user.github_login.chars().collect(),
-//                         }),
-//                 );
-//             let matches = executor.block(match_strings(
-//                 &self.match_candidates,
-//                 &query,
-//                 true,
-//                 usize::MAX,
-//                 &Default::default(),
-//                 executor.clone(),
-//             ));
-//             request_entries.extend(
-//                 matches
-//                     .iter()
-//                     .map(|mat| ListEntry::OutgoingRequest(outgoing[mat.candidate_id].clone())),
-//             );
-//         }
-
-//         if !request_entries.is_empty() {
-//             self.entries
-//                 .push(ListEntry::Header(Section::ContactRequests));
-//             if !self.collapsed_sections.contains(&Section::ContactRequests) {
-//                 self.entries.append(&mut request_entries);
-//             }
-//         }
-
-//         let contacts = user_store.contacts();
-//         if !contacts.is_empty() {
-//             self.match_candidates.clear();
-//             self.match_candidates
-//                 .extend(
-//                     contacts
-//                         .iter()
-//                         .enumerate()
-//                         .map(|(ix, contact)| StringMatchCandidate {
-//                             id: ix,
-//                             string: contact.user.github_login.clone(),
-//                             char_bag: contact.user.github_login.chars().collect(),
-//                         }),
-//                 );
-
-//             let matches = executor.block(match_strings(
-//                 &self.match_candidates,
-//                 &query,
-//                 true,
-//                 usize::MAX,
-//                 &Default::default(),
-//                 executor.clone(),
-//             ));
-
-//             let (online_contacts, offline_contacts) = matches
-//                 .iter()
-//                 .partition::<Vec<_>, _>(|mat| contacts[mat.candidate_id].online);
-
-//             for (matches, section) in [
-//                 (online_contacts, Section::Online),
-//                 (offline_contacts, Section::Offline),
-//             ] {
-//                 if !matches.is_empty() {
-//                     self.entries.push(ListEntry::Header(section));
-//                     if !self.collapsed_sections.contains(&section) {
-//                         let active_call = &ActiveCall::global(cx).read(cx);
-//                         for mat in matches {
-//                             let contact = &contacts[mat.candidate_id];
-//                             self.entries.push(ListEntry::Contact {
-//                                 contact: contact.clone(),
-//                                 calling: active_call.pending_invites().contains(&contact.user.id),
-//                             });
-//                         }
-//                     }
-//                 }
-//             }
-//         }
-
-//         if incoming.is_empty() && outgoing.is_empty() && contacts.is_empty() {
-//             self.entries.push(ListEntry::ContactPlaceholder);
-//         }
-
-//         if select_same_item {
-//             if let Some(prev_selected_entry) = prev_selected_entry {
-//                 self.selection.take();
-//                 for (ix, entry) in self.entries.iter().enumerate() {
-//                     if *entry == prev_selected_entry {
-//                         self.selection = Some(ix);
-//                         break;
-//                     }
-//                 }
-//             }
-//         } else {
-//             self.selection = self.selection.and_then(|prev_selection| {
-//                 if self.entries.is_empty() {
-//                     None
-//                 } else {
-//                     Some(prev_selection.min(self.entries.len() - 1))
-//                 }
-//             });
-//         }
-
-//         let old_scroll_top = self.list_state.logical_scroll_top();
-
-//         self.list_state.reset(self.entries.len());
-
-//         if scroll_to_top {
-//             self.list_state.scroll_to(ListOffset::default());
-//         } else {
-//             // Attempt to maintain the same scroll position.
-//             if let Some(old_top_entry) = old_entries.get(old_scroll_top.item_ix) {
-//                 let new_scroll_top = self
-//                     .entries
-//                     .iter()
-//                     .position(|entry| entry == old_top_entry)
-//                     .map(|item_ix| ListOffset {
-//                         item_ix,
-//                         offset_in_item: old_scroll_top.offset_in_item,
-//                     })
-//                     .or_else(|| {
-//                         let entry_after_old_top = old_entries.get(old_scroll_top.item_ix + 1)?;
-//                         let item_ix = self
-//                             .entries
-//                             .iter()
-//                             .position(|entry| entry == entry_after_old_top)?;
-//                         Some(ListOffset {
-//                             item_ix,
-//                             offset_in_item: 0.,
-//                         })
-//                     })
-//                     .or_else(|| {
-//                         let entry_before_old_top =
-//                             old_entries.get(old_scroll_top.item_ix.saturating_sub(1))?;
-//                         let item_ix = self
-//                             .entries
-//                             .iter()
-//                             .position(|entry| entry == entry_before_old_top)?;
-//                         Some(ListOffset {
-//                             item_ix,
-//                             offset_in_item: 0.,
-//                         })
-//                     });
-
-//                 self.list_state
-//                     .scroll_to(new_scroll_top.unwrap_or(old_scroll_top));
-//             }
-//         }
-
-//         cx.notify();
-//     }
-
-//     fn render_call_participant(
-//         user: &User,
-//         peer_id: Option<PeerId>,
-//         user_store: ModelHandle<UserStore>,
-//         is_pending: bool,
-//         is_selected: bool,
-//         theme: &theme::Theme,
-//         cx: &mut ViewContext<Self>,
-//     ) -> AnyElement<Self> {
-//         enum CallParticipant {}
-//         enum CallParticipantTooltip {}
-//         enum LeaveCallButton {}
-//         enum LeaveCallTooltip {}
-
-//         let collab_theme = &theme.collab_panel;
-
-//         let is_current_user =
-//             user_store.read(cx).current_user().map(|user| user.id) == Some(user.id);
-
-//         let content = MouseEventHandler::new::<CallParticipant, _>(
-//             user.id as usize,
-//             cx,
-//             |mouse_state, cx| {
-//                 let style = if is_current_user {
-//                     *collab_theme
-//                         .contact_row
-//                         .in_state(is_selected)
-//                         .style_for(&mut Default::default())
-//                 } else {
-//                     *collab_theme
-//                         .contact_row
-//                         .in_state(is_selected)
-//                         .style_for(mouse_state)
-//                 };
-
-//                 Flex::row()
-//                     .with_children(user.avatar.clone().map(|avatar| {
-//                         Image::from_data(avatar)
-//                             .with_style(collab_theme.contact_avatar)
-//                             .aligned()
-//                             .left()
-//                     }))
-//                     .with_child(
-//                         Label::new(
-//                             user.github_login.clone(),
-//                             collab_theme.contact_username.text.clone(),
-//                         )
-//                         .contained()
-//                         .with_style(collab_theme.contact_username.container)
-//                         .aligned()
-//                         .left()
-//                         .flex(1., true),
-//                     )
-//                     .with_children(if is_pending {
-//                         Some(
-//                             Label::new("Calling", collab_theme.calling_indicator.text.clone())
-//                                 .contained()
-//                                 .with_style(collab_theme.calling_indicator.container)
-//                                 .aligned()
-//                                 .into_any(),
-//                         )
-//                     } else if is_current_user {
-//                         Some(
-//                             MouseEventHandler::new::<LeaveCallButton, _>(0, cx, |state, _| {
-//                                 render_icon_button(
-//                                     theme
-//                                         .collab_panel
-//                                         .leave_call_button
-//                                         .style_for(is_selected, state),
-//                                     "icons/exit.svg",
-//                                 )
-//                             })
-//                             .with_cursor_style(CursorStyle::PointingHand)
-//                             .on_click(MouseButton::Left, |_, _, cx| {
-//                                 Self::leave_call(cx);
-//                             })
-//                             .with_tooltip::<LeaveCallTooltip>(
-//                                 0,
-//                                 "Leave call",
-//                                 None,
-//                                 theme.tooltip.clone(),
-//                                 cx,
-//                             )
-//                             .into_any(),
-//                         )
-//                     } else {
-//                         None
-//                     })
-//                     .constrained()
-//                     .with_height(collab_theme.row_height)
-//                     .contained()
-//                     .with_style(style)
-//             },
-//         );
-
-//         if is_current_user || is_pending || peer_id.is_none() {
-//             return content.into_any();
-//         }
-
-//         let tooltip = format!("Follow {}", user.github_login);
-
-//         content
-//             .on_click(MouseButton::Left, move |_, this, cx| {
-//                 if let Some(workspace) = this.workspace.upgrade(cx) {
-//                     workspace
-//                         .update(cx, |workspace, cx| workspace.follow(peer_id.unwrap(), cx))
-//                         .map(|task| task.detach_and_log_err(cx));
-//                 }
-//             })
-//             .with_cursor_style(CursorStyle::PointingHand)
-//             .with_tooltip::<CallParticipantTooltip>(
-//                 user.id as usize,
-//                 tooltip,
-//                 Some(Box::new(FollowNextCollaborator)),
-//                 theme.tooltip.clone(),
-//                 cx,
-//             )
-//             .into_any()
-//     }
-
-//     fn render_participant_project(
-//         project_id: u64,
-//         worktree_root_names: &[String],
-//         host_user_id: u64,
-//         is_current: bool,
-//         is_last: bool,
-//         is_selected: bool,
-//         theme: &theme::Theme,
-//         cx: &mut ViewContext<Self>,
-//     ) -> AnyElement<Self> {
-//         enum JoinProject {}
-//         enum JoinProjectTooltip {}
-
-//         let collab_theme = &theme.collab_panel;
-//         let host_avatar_width = collab_theme
-//             .contact_avatar
-//             .width
-//             .or(collab_theme.contact_avatar.height)
-//             .unwrap_or(0.);
-//         let tree_branch = collab_theme.tree_branch;
-//         let project_name = if worktree_root_names.is_empty() {
-//             "untitled".to_string()
-//         } else {
-//             worktree_root_names.join(", ")
-//         };
-
-//         let content =
-//             MouseEventHandler::new::<JoinProject, _>(project_id as usize, cx, |mouse_state, cx| {
-//                 let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
-//                 let row = if is_current {
-//                     collab_theme
-//                         .project_row
-//                         .in_state(true)
-//                         .style_for(&mut Default::default())
-//                 } else {
-//                     collab_theme
-//                         .project_row
-//                         .in_state(is_selected)
-//                         .style_for(mouse_state)
-//                 };
-
-//                 Flex::row()
-//                     .with_child(render_tree_branch(
-//                         tree_branch,
-//                         &row.name.text,
-//                         is_last,
-//                         vec2f(host_avatar_width, collab_theme.row_height),
-//                         cx.font_cache(),
-//                     ))
-//                     .with_child(
-//                         Svg::new("icons/file_icons/folder.svg")
-//                             .with_color(collab_theme.channel_hash.color)
-//                             .constrained()
-//                             .with_width(collab_theme.channel_hash.width)
-//                             .aligned()
-//                             .left(),
-//                     )
-//                     .with_child(
-//                         Label::new(project_name.clone(), row.name.text.clone())
-//                             .aligned()
-//                             .left()
-//                             .contained()
-//                             .with_style(row.name.container)
-//                             .flex(1., false),
-//                     )
-//                     .constrained()
-//                     .with_height(collab_theme.row_height)
-//                     .contained()
-//                     .with_style(row.container)
-//             });
-
-//         if is_current {
-//             return content.into_any();
-//         }
-
-//         content
-//             .with_cursor_style(CursorStyle::PointingHand)
-//             .on_click(MouseButton::Left, move |_, this, cx| {
-//                 if let Some(workspace) = this.workspace.upgrade(cx) {
-//                     let app_state = workspace.read(cx).app_state().clone();
-//                     workspace::join_remote_project(project_id, host_user_id, app_state, cx)
-//                         .detach_and_log_err(cx);
-//                 }
-//             })
-//             .with_tooltip::<JoinProjectTooltip>(
-//                 project_id as usize,
-//                 format!("Open {}", project_name),
-//                 None,
-//                 theme.tooltip.clone(),
-//                 cx,
-//             )
-//             .into_any()
-//     }
-
-//     fn render_participant_screen(
-//         peer_id: Option<PeerId>,
-//         is_last: bool,
-//         is_selected: bool,
-//         theme: &theme::CollabPanel,
-//         cx: &mut ViewContext<Self>,
-//     ) -> AnyElement<Self> {
-//         enum OpenSharedScreen {}
-
-//         let host_avatar_width = theme
-//             .contact_avatar
-//             .width
-//             .or(theme.contact_avatar.height)
-//             .unwrap_or(0.);
-//         let tree_branch = theme.tree_branch;
-
-//         let handler = MouseEventHandler::new::<OpenSharedScreen, _>(
-//             peer_id.map(|id| id.as_u64()).unwrap_or(0) as usize,
-//             cx,
-//             |mouse_state, cx| {
-//                 let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
-//                 let row = theme
-//                     .project_row
-//                     .in_state(is_selected)
-//                     .style_for(mouse_state);
-
-//                 Flex::row()
-//                     .with_child(render_tree_branch(
-//                         tree_branch,
-//                         &row.name.text,
-//                         is_last,
-//                         vec2f(host_avatar_width, theme.row_height),
-//                         cx.font_cache(),
-//                     ))
-//                     .with_child(
-//                         Svg::new("icons/desktop.svg")
-//                             .with_color(theme.channel_hash.color)
-//                             .constrained()
-//                             .with_width(theme.channel_hash.width)
-//                             .aligned()
-//                             .left(),
-//                     )
-//                     .with_child(
-//                         Label::new("Screen", row.name.text.clone())
-//                             .aligned()
-//                             .left()
-//                             .contained()
-//                             .with_style(row.name.container)
-//                             .flex(1., false),
-//                     )
-//                     .constrained()
-//                     .with_height(theme.row_height)
-//                     .contained()
-//                     .with_style(row.container)
-//             },
-//         );
-//         if peer_id.is_none() {
-//             return handler.into_any();
-//         }
-//         handler
-//             .with_cursor_style(CursorStyle::PointingHand)
-//             .on_click(MouseButton::Left, move |_, this, cx| {
-//                 if let Some(workspace) = this.workspace.upgrade(cx) {
-//                     workspace.update(cx, |workspace, cx| {
-//                         workspace.open_shared_screen(peer_id.unwrap(), cx)
-//                     });
-//                 }
-//             })
-//             .into_any()
-//     }
-
-//     fn take_editing_state(&mut self, cx: &mut ViewContext<Self>) -> bool {
-//         if let Some(_) = self.channel_editing_state.take() {
-//             self.channel_name_editor.update(cx, |editor, cx| {
-//                 editor.set_text("", cx);
-//             });
-//             true
-//         } else {
-//             false
-//         }
-//     }
-
-//     fn render_header(
-//         &self,
-//         section: Section,
-//         theme: &theme::Theme,
-//         is_selected: bool,
-//         is_collapsed: bool,
-//         cx: &mut ViewContext<Self>,
-//     ) -> AnyElement<Self> {
-//         enum Header {}
-//         enum LeaveCallContactList {}
-//         enum AddChannel {}
-
-//         let tooltip_style = &theme.tooltip;
-//         let mut channel_link = None;
-//         let mut channel_tooltip_text = None;
-//         let mut channel_icon = None;
-//         let mut is_dragged_over = false;
-
-//         let text = match section {
-//             Section::ActiveCall => {
-//                 let channel_name = maybe!({
-//                     let channel_id = ActiveCall::global(cx).read(cx).channel_id(cx)?;
-
-//                     let channel = self.channel_store.read(cx).channel_for_id(channel_id)?;
-
-//                     channel_link = Some(channel.link());
-//                     (channel_icon, channel_tooltip_text) = match channel.visibility {
-//                         proto::ChannelVisibility::Public => {
-//                             (Some("icons/public.svg"), Some("Copy public channel link."))
-//                         }
-//                         proto::ChannelVisibility::Members => {
-//                             (Some("icons/hash.svg"), Some("Copy private channel link."))
-//                         }
-//                     };
-
-//                     Some(channel.name.as_str())
-//                 });
-
-//                 if let Some(name) = channel_name {
-//                     Cow::Owned(format!("{}", name))
-//                 } else {
-//                     Cow::Borrowed("Current Call")
-//                 }
-//             }
-//             Section::ContactRequests => Cow::Borrowed("Requests"),
-//             Section::Contacts => Cow::Borrowed("Contacts"),
-//             Section::Channels => Cow::Borrowed("Channels"),
-//             Section::ChannelInvites => Cow::Borrowed("Invites"),
-//             Section::Online => Cow::Borrowed("Online"),
-//             Section::Offline => Cow::Borrowed("Offline"),
-//         };
-
-//         enum AddContact {}
-//         let button = match section {
-//             Section::ActiveCall => channel_link.map(|channel_link| {
-//                 let channel_link_copy = channel_link.clone();
-//                 MouseEventHandler::new::<AddContact, _>(0, cx, |state, _| {
-//                     render_icon_button(
-//                         theme
-//                             .collab_panel
-//                             .leave_call_button
-//                             .style_for(is_selected, state),
-//                         "icons/link.svg",
-//                     )
-//                 })
-//                 .with_cursor_style(CursorStyle::PointingHand)
-//                 .on_click(MouseButton::Left, move |_, _, cx| {
-//                     let item = ClipboardItem::new(channel_link_copy.clone());
-//                     cx.write_to_clipboard(item)
-//                 })
-//                 .with_tooltip::<AddContact>(
-//                     0,
-//                     channel_tooltip_text.unwrap(),
-//                     None,
-//                     tooltip_style.clone(),
-//                     cx,
-//                 )
-//             }),
-//             Section::Contacts => Some(
-//                 MouseEventHandler::new::<LeaveCallContactList, _>(0, cx, |state, _| {
-//                     render_icon_button(
-//                         theme
-//                             .collab_panel
-//                             .add_contact_button
-//                             .style_for(is_selected, state),
-//                         "icons/plus.svg",
-//                     )
-//                 })
-//                 .with_cursor_style(CursorStyle::PointingHand)
-//                 .on_click(MouseButton::Left, |_, this, cx| {
-//                     this.toggle_contact_finder(cx);
-//                 })
-//                 .with_tooltip::<LeaveCallContactList>(
-//                     0,
-//                     "Search for new contact",
-//                     None,
-//                     tooltip_style.clone(),
-//                     cx,
-//                 ),
-//             ),
-//             Section::Channels => {
-//                 if cx
-//                     .global::<DragAndDrop<Workspace>>()
-//                     .currently_dragged::<Channel>(cx.window())
-//                     .is_some()
-//                     && self.drag_target_channel == ChannelDragTarget::Root
-//                 {
-//                     is_dragged_over = true;
-//                 }
-
-//                 Some(
-//                     MouseEventHandler::new::<AddChannel, _>(0, cx, |state, _| {
-//                         render_icon_button(
-//                             theme
-//                                 .collab_panel
-//                                 .add_contact_button
-//                                 .style_for(is_selected, state),
-//                             "icons/plus.svg",
-//                         )
-//                     })
-//                     .with_cursor_style(CursorStyle::PointingHand)
-//                     .on_click(MouseButton::Left, |_, this, cx| this.new_root_channel(cx))
-//                     .with_tooltip::<AddChannel>(
-//                         0,
-//                         "Create a channel",
-//                         None,
-//                         tooltip_style.clone(),
-//                         cx,
-//                     ),
-//                 )
-//             }
-//             _ => None,
-//         };
-
-//         let can_collapse = match section {
-//             Section::ActiveCall | Section::Channels | Section::Contacts => false,
-//             Section::ChannelInvites
-//             | Section::ContactRequests
-//             | Section::Online
-//             | Section::Offline => true,
-//         };
-//         let icon_size = (&theme.collab_panel).section_icon_size;
-//         let mut result = MouseEventHandler::new::<Header, _>(section as usize, cx, |state, _| {
-//             let header_style = if can_collapse {
-//                 theme
-//                     .collab_panel
-//                     .subheader_row
-//                     .in_state(is_selected)
-//                     .style_for(state)
-//             } else {
-//                 &theme.collab_panel.header_row
-//             };
-
-//             Flex::row()
-//                 .with_children(if can_collapse {
-//                     Some(
-//                         Svg::new(if is_collapsed {
-//                             "icons/chevron_right.svg"
-//                         } else {
-//                             "icons/chevron_down.svg"
-//                         })
-//                         .with_color(header_style.text.color)
-//                         .constrained()
-//                         .with_max_width(icon_size)
-//                         .with_max_height(icon_size)
-//                         .aligned()
-//                         .constrained()
-//                         .with_width(icon_size)
-//                         .contained()
-//                         .with_margin_right(
-//                             theme.collab_panel.contact_username.container.margin.left,
-//                         ),
-//                     )
-//                 } else if let Some(channel_icon) = channel_icon {
-//                     Some(
-//                         Svg::new(channel_icon)
-//                             .with_color(header_style.text.color)
-//                             .constrained()
-//                             .with_max_width(icon_size)
-//                             .with_max_height(icon_size)
-//                             .aligned()
-//                             .constrained()
-//                             .with_width(icon_size)
-//                             .contained()
-//                             .with_margin_right(
-//                                 theme.collab_panel.contact_username.container.margin.left,
-//                             ),
-//                     )
-//                 } else {
-//                     None
-//                 })
-//                 .with_child(
-//                     Label::new(text, header_style.text.clone())
-//                         .aligned()
-//                         .left()
-//                         .flex(1., true),
-//                 )
-//                 .with_children(button.map(|button| button.aligned().right()))
-//                 .constrained()
-//                 .with_height(theme.collab_panel.row_height)
-//                 .contained()
-//                 .with_style(if is_dragged_over {
-//                     theme.collab_panel.dragged_over_header
-//                 } else {
-//                     header_style.container
-//                 })
-//         });
-
-//         result = result
-//             .on_move(move |_, this, cx| {
-//                 if cx
-//                     .global::<DragAndDrop<Workspace>>()
-//                     .currently_dragged::<Channel>(cx.window())
-//                     .is_some()
-//                 {
-//                     this.drag_target_channel = ChannelDragTarget::Root;
-//                     cx.notify()
-//                 }
-//             })
-//             .on_up(MouseButton::Left, move |_, this, cx| {
-//                 if let Some((_, dragged_channel)) = cx
-//                     .global::<DragAndDrop<Workspace>>()
-//                     .currently_dragged::<Channel>(cx.window())
-//                 {
-//                     this.channel_store
-//                         .update(cx, |channel_store, cx| {
-//                             channel_store.move_channel(dragged_channel.id, None, cx)
-//                         })
-//                         .detach_and_log_err(cx)
-//                 }
-//             });
-
-//         if can_collapse {
-//             result = result
-//                 .with_cursor_style(CursorStyle::PointingHand)
-//                 .on_click(MouseButton::Left, move |_, this, cx| {
-//                     if can_collapse {
-//                         this.toggle_section_expanded(section, cx);
-//                     }
-//                 })
-//         }
-
-//         result.into_any()
-//     }
-
-//     fn render_contact(
-//         contact: &Contact,
-//         calling: bool,
-//         project: &ModelHandle<Project>,
-//         theme: &theme::Theme,
-//         is_selected: bool,
-//         cx: &mut ViewContext<Self>,
-//     ) -> AnyElement<Self> {
-//         enum ContactTooltip {}
-
-//         let collab_theme = &theme.collab_panel;
-//         let online = contact.online;
-//         let busy = contact.busy || calling;
-//         let user_id = contact.user.id;
-//         let github_login = contact.user.github_login.clone();
-//         let initial_project = project.clone();
-
-//         let event_handler =
-//             MouseEventHandler::new::<Contact, _>(contact.user.id as usize, cx, |state, cx| {
-//                 Flex::row()
-//                     .with_children(contact.user.avatar.clone().map(|avatar| {
-//                         let status_badge = if contact.online {
-//                             Some(
-//                                 Empty::new()
-//                                     .collapsed()
-//                                     .contained()
-//                                     .with_style(if busy {
-//                                         collab_theme.contact_status_busy
-//                                     } else {
-//                                         collab_theme.contact_status_free
-//                                     })
-//                                     .aligned(),
-//                             )
-//                         } else {
-//                             None
-//                         };
-//                         Stack::new()
-//                             .with_child(
-//                                 Image::from_data(avatar)
-//                                     .with_style(collab_theme.contact_avatar)
-//                                     .aligned()
-//                                     .left(),
-//                             )
-//                             .with_children(status_badge)
-//                     }))
-//                     .with_child(
-//                         Label::new(
-//                             contact.user.github_login.clone(),
-//                             collab_theme.contact_username.text.clone(),
-//                         )
-//                         .contained()
-//                         .with_style(collab_theme.contact_username.container)
-//                         .aligned()
-//                         .left()
-//                         .flex(1., true),
-//                     )
-//                     .with_children(if state.hovered() {
-//                         Some(
-//                             MouseEventHandler::new::<Cancel, _>(
-//                                 contact.user.id as usize,
-//                                 cx,
-//                                 |mouse_state, _| {
-//                                     let button_style =
-//                                         collab_theme.contact_button.style_for(mouse_state);
-//                                     render_icon_button(button_style, "icons/x.svg")
-//                                         .aligned()
-//                                         .flex_float()
-//                                 },
-//                             )
-//                             .with_padding(Padding::uniform(2.))
-//                             .with_cursor_style(CursorStyle::PointingHand)
-//                             .on_click(MouseButton::Left, move |_, this, cx| {
-//                                 this.remove_contact(user_id, &github_login, cx);
-//                             })
-//                             .flex_float(),
-//                         )
-//                     } else {
-//                         None
-//                     })
-//                     .with_children(if calling {
-//                         Some(
-//                             Label::new("Calling", collab_theme.calling_indicator.text.clone())
-//                                 .contained()
-//                                 .with_style(collab_theme.calling_indicator.container)
-//                                 .aligned(),
-//                         )
-//                     } else {
-//                         None
-//                     })
-//                     .constrained()
-//                     .with_height(collab_theme.row_height)
-//                     .contained()
-//                     .with_style(
-//                         *collab_theme
-//                             .contact_row
-//                             .in_state(is_selected)
-//                             .style_for(state),
-//                     )
-//             });
-
-//         if online && !busy {
-//             let room = ActiveCall::global(cx).read(cx).room();
-//             let label = if room.is_some() {
-//                 format!("Invite {} to join call", contact.user.github_login)
-//             } else {
-//                 format!("Call {}", contact.user.github_login)
-//             };
-
-//             event_handler
-//                 .on_click(MouseButton::Left, move |_, this, cx| {
-//                     this.call(user_id, Some(initial_project.clone()), cx);
-//                 })
-//                 .with_cursor_style(CursorStyle::PointingHand)
-//                 .with_tooltip::<ContactTooltip>(
-//                     contact.user.id as usize,
-//                     label,
-//                     None,
-//                     theme.tooltip.clone(),
-//                     cx,
-//                 )
-//                 .into_any()
-//         } else {
-//             event_handler
-//                 .with_tooltip::<ContactTooltip>(
-//                     contact.user.id as usize,
-//                     format!(
-//                         "{} is {}",
-//                         contact.user.github_login,
-//                         if busy { "on a call" } else { "offline" }
-//                     ),
-//                     None,
-//                     theme.tooltip.clone(),
-//                     cx,
-//                 )
-//                 .into_any()
-//         }
-//     }
-
-//     fn render_contact_placeholder(
-//         &self,
-//         theme: &theme::CollabPanel,
-//         is_selected: bool,
-//         cx: &mut ViewContext<Self>,
-//     ) -> AnyElement<Self> {
-//         enum AddContacts {}
-//         MouseEventHandler::new::<AddContacts, _>(0, cx, |state, _| {
-//             let style = theme.list_empty_state.style_for(is_selected, state);
-//             Flex::row()
-//                 .with_child(
-//                     Svg::new("icons/plus.svg")
-//                         .with_color(theme.list_empty_icon.color)
-//                         .constrained()
-//                         .with_width(theme.list_empty_icon.width)
-//                         .aligned()
-//                         .left(),
-//                 )
-//                 .with_child(
-//                     Label::new("Add a contact", style.text.clone())
-//                         .contained()
-//                         .with_style(theme.list_empty_label_container),
-//                 )
-//                 .align_children_center()
-//                 .contained()
-//                 .with_style(style.container)
-//                 .into_any()
-//         })
-//         .on_click(MouseButton::Left, |_, this, cx| {
-//             this.toggle_contact_finder(cx);
-//         })
-//         .into_any()
-//     }
-
-//     fn render_channel_editor(
-//         &self,
-//         theme: &theme::Theme,
-//         depth: usize,
-//         cx: &AppContext,
-//     ) -> AnyElement<Self> {
-//         Flex::row()
-//             .with_child(
-//                 Empty::new()
-//                     .constrained()
-//                     .with_width(theme.collab_panel.disclosure.button_space()),
-//             )
-//             .with_child(
-//                 Svg::new("icons/hash.svg")
-//                     .with_color(theme.collab_panel.channel_hash.color)
-//                     .constrained()
-//                     .with_width(theme.collab_panel.channel_hash.width)
-//                     .aligned()
-//                     .left(),
-//             )
-//             .with_child(
-//                 if let Some(pending_name) = self
-//                     .channel_editing_state
-//                     .as_ref()
-//                     .and_then(|state| state.pending_name())
-//                 {
-//                     Label::new(
-//                         pending_name.to_string(),
-//                         theme.collab_panel.contact_username.text.clone(),
-//                     )
-//                     .contained()
-//                     .with_style(theme.collab_panel.contact_username.container)
-//                     .aligned()
-//                     .left()
-//                     .flex(1., true)
-//                     .into_any()
-//                 } else {
-//                     ChildView::new(&self.channel_name_editor, cx)
-//                         .aligned()
-//                         .left()
-//                         .contained()
-//                         .with_style(theme.collab_panel.channel_editor)
-//                         .flex(1.0, true)
-//                         .into_any()
-//                 },
-//             )
-//             .align_children_center()
-//             .constrained()
-//             .with_height(theme.collab_panel.row_height)
-//             .contained()
-//             .with_style(ContainerStyle {
-//                 background_color: Some(theme.editor.background),
-//                 ..*theme.collab_panel.contact_row.default_style()
-//             })
-//             .with_padding_left(
-//                 theme.collab_panel.contact_row.default_style().padding.left
-//                     + theme.collab_panel.channel_indent * depth as f32,
-//             )
-//             .into_any()
-//     }
-
-//     fn render_channel(
-//         &self,
-//         channel: &Channel,
-//         depth: usize,
-//         theme: &theme::Theme,
-//         is_selected: bool,
-//         has_children: bool,
-//         ix: usize,
-//         cx: &mut ViewContext<Self>,
-//     ) -> AnyElement<Self> {
-//         let channel_id = channel.id;
-//         let collab_theme = &theme.collab_panel;
-//         let is_public = self
-//             .channel_store
-//             .read(cx)
-//             .channel_for_id(channel_id)
-//             .map(|channel| channel.visibility)
-//             == Some(proto::ChannelVisibility::Public);
-//         let other_selected = self.selected_channel().map(|channel| channel.id) == Some(channel.id);
-//         let disclosed =
-//             has_children.then(|| !self.collapsed_channels.binary_search(&channel.id).is_ok());
-
-//         let is_active = maybe!({
-//             let call_channel = ActiveCall::global(cx)
-//                 .read(cx)
-//                 .room()?
-//                 .read(cx)
-//                 .channel_id()?;
-//             Some(call_channel == channel_id)
-//         })
-//         .unwrap_or(false);
-
-//         const FACEPILE_LIMIT: usize = 3;
-
-//         enum ChannelCall {}
-//         enum ChannelNote {}
-//         enum NotesTooltip {}
-//         enum ChatTooltip {}
-//         enum ChannelTooltip {}
-
-//         let mut is_dragged_over = false;
-//         if cx
-//             .global::<DragAndDrop<Workspace>>()
-//             .currently_dragged::<Channel>(cx.window())
-//             .is_some()
-//             && self.drag_target_channel == ChannelDragTarget::Channel(channel_id)
-//         {
-//             is_dragged_over = true;
-//         }
-
-//         let has_messages_notification = channel.unseen_message_id.is_some();
-
-//         MouseEventHandler::new::<Channel, _>(ix, cx, |state, cx| {
-//             let row_hovered = state.hovered();
-
-//             let mut select_state = |interactive: &Interactive<ContainerStyle>| {
-//                 if state.clicked() == Some(MouseButton::Left) && interactive.clicked.is_some() {
-//                     interactive.clicked.as_ref().unwrap().clone()
-//                 } else if state.hovered() || other_selected {
-//                     interactive
-//                         .hovered
-//                         .as_ref()
-//                         .unwrap_or(&interactive.default)
-//                         .clone()
-//                 } else {
-//                     interactive.default.clone()
-//                 }
-//             };
-
-//             Flex::<Self>::row()
-//                 .with_child(
-//                     Svg::new(if is_public {
-//                         "icons/public.svg"
-//                     } else {
-//                         "icons/hash.svg"
-//                     })
-//                     .with_color(collab_theme.channel_hash.color)
-//                     .constrained()
-//                     .with_width(collab_theme.channel_hash.width)
-//                     .aligned()
-//                     .left(),
-//                 )
-//                 .with_child({
-//                     let style = collab_theme.channel_name.inactive_state();
-//                     Flex::row()
-//                         .with_child(
-//                             Label::new(channel.name.clone(), style.text.clone())
-//                                 .contained()
-//                                 .with_style(style.container)
-//                                 .aligned()
-//                                 .left()
-//                                 .with_tooltip::<ChannelTooltip>(
-//                                     ix,
-//                                     "Join channel",
-//                                     None,
-//                                     theme.tooltip.clone(),
-//                                     cx,
-//                                 ),
-//                         )
-//                         .with_children({
-//                             let participants =
-//                                 self.channel_store.read(cx).channel_participants(channel_id);
-
-//                             if !participants.is_empty() {
-//                                 let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT);
-
-//                                 let result = FacePile::new(collab_theme.face_overlap)
-//                                     .with_children(
-//                                         participants
-//                                             .iter()
-//                                             .filter_map(|user| {
-//                                                 Some(
-//                                                     Image::from_data(user.avatar.clone()?)
-//                                                         .with_style(collab_theme.channel_avatar),
-//                                                 )
-//                                             })
-//                                             .take(FACEPILE_LIMIT),
-//                                     )
-//                                     .with_children((extra_count > 0).then(|| {
-//                                         Label::new(
-//                                             format!("+{}", extra_count),
-//                                             collab_theme.extra_participant_label.text.clone(),
-//                                         )
-//                                         .contained()
-//                                         .with_style(collab_theme.extra_participant_label.container)
-//                                     }));
-
-//                                 Some(result)
-//                             } else {
-//                                 None
-//                             }
-//                         })
-//                         .with_spacing(8.)
-//                         .align_children_center()
-//                         .flex(1., true)
-//                 })
-//                 .with_child(
-//                     MouseEventHandler::new::<ChannelNote, _>(ix, cx, move |mouse_state, _| {
-//                         let container_style = collab_theme
-//                             .disclosure
-//                             .button
-//                             .style_for(mouse_state)
-//                             .container;
-
-//                         if channel.unseen_message_id.is_some() {
-//                             Svg::new("icons/conversations.svg")
-//                                 .with_color(collab_theme.channel_note_active_color)
-//                                 .constrained()
-//                                 .with_width(collab_theme.channel_hash.width)
-//                                 .contained()
-//                                 .with_style(container_style)
-//                                 .with_uniform_padding(4.)
-//                                 .into_any()
-//                         } else if row_hovered {
-//                             Svg::new("icons/conversations.svg")
-//                                 .with_color(collab_theme.channel_hash.color)
-//                                 .constrained()
-//                                 .with_width(collab_theme.channel_hash.width)
-//                                 .contained()
-//                                 .with_style(container_style)
-//                                 .with_uniform_padding(4.)
-//                                 .into_any()
-//                         } else {
-//                             Empty::new().into_any()
-//                         }
-//                     })
-//                     .on_click(MouseButton::Left, move |_, this, cx| {
-//                         this.join_channel_chat(&JoinChannelChat { channel_id }, cx);
-//                     })
-//                     .with_tooltip::<ChatTooltip>(
-//                         ix,
-//                         "Open channel chat",
-//                         None,
-//                         theme.tooltip.clone(),
-//                         cx,
-//                     )
-//                     .contained()
-//                     .with_margin_right(4.),
-//                 )
-//                 .with_child(
-//                     MouseEventHandler::new::<ChannelCall, _>(ix, cx, move |mouse_state, cx| {
-//                         let container_style = collab_theme
-//                             .disclosure
-//                             .button
-//                             .style_for(mouse_state)
-//                             .container;
-//                         if row_hovered || channel.unseen_note_version.is_some() {
-//                             Svg::new("icons/file.svg")
-//                                 .with_color(if channel.unseen_note_version.is_some() {
-//                                     collab_theme.channel_note_active_color
-//                                 } else {
-//                                     collab_theme.channel_hash.color
-//                                 })
-//                                 .constrained()
-//                                 .with_width(collab_theme.channel_hash.width)
-//                                 .contained()
-//                                 .with_style(container_style)
-//                                 .with_uniform_padding(4.)
-//                                 .with_margin_right(collab_theme.channel_hash.container.margin.left)
-//                                 .with_tooltip::<NotesTooltip>(
-//                                     ix as usize,
-//                                     "Open channel notes",
-//                                     None,
-//                                     theme.tooltip.clone(),
-//                                     cx,
-//                                 )
-//                                 .into_any()
-//                         } else if has_messages_notification {
-//                             Empty::new()
-//                                 .constrained()
-//                                 .with_width(collab_theme.channel_hash.width)
-//                                 .contained()
-//                                 .with_uniform_padding(4.)
-//                                 .with_margin_right(collab_theme.channel_hash.container.margin.left)
-//                                 .into_any()
-//                         } else {
-//                             Empty::new().into_any()
-//                         }
-//                     })
-//                     .on_click(MouseButton::Left, move |_, this, cx| {
-//                         this.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
-//                     }),
-//                 )
-//                 .align_children_center()
-//                 .styleable_component()
-//                 .disclosable(
-//                     disclosed,
-//                     Box::new(ToggleCollapse {
-//                         location: channel.id.clone(),
-//                     }),
-//                 )
-//                 .with_id(ix)
-//                 .with_style(collab_theme.disclosure.clone())
-//                 .element()
-//                 .constrained()
-//                 .with_height(collab_theme.row_height)
-//                 .contained()
-//                 .with_style(select_state(
-//                     collab_theme
-//                         .channel_row
-//                         .in_state(is_selected || is_active || is_dragged_over),
-//                 ))
-//                 .with_padding_left(
-//                     collab_theme.channel_row.default_style().padding.left
-//                         + collab_theme.channel_indent * depth as f32,
-//                 )
-//         })
-//         .on_click(MouseButton::Left, move |_, this, cx| {
-//             if this.drag_target_channel == ChannelDragTarget::None {
-//                 if is_active {
-//                     this.open_channel_notes(&OpenChannelNotes { channel_id }, cx)
-//                 } else {
-//                     this.join_channel(channel_id, cx)
-//                 }
-//             }
-//         })
-//         .on_click(MouseButton::Right, {
-//             let channel = channel.clone();
-//             move |e, this, cx| {
-//                 this.deploy_channel_context_menu(Some(e.position), &channel, ix, cx);
-//             }
-//         })
-//         .on_up(MouseButton::Left, move |_, this, cx| {
-//             if let Some((_, dragged_channel)) = cx
-//                 .global::<DragAndDrop<Workspace>>()
-//                 .currently_dragged::<Channel>(cx.window())
-//             {
-//                 this.channel_store
-//                     .update(cx, |channel_store, cx| {
-//                         channel_store.move_channel(dragged_channel.id, Some(channel_id), cx)
-//                     })
-//                     .detach_and_log_err(cx)
-//             }
-//         })
-//         .on_move({
-//             let channel = channel.clone();
-//             move |_, this, cx| {
-//                 if let Some((_, dragged_channel)) = cx
-//                     .global::<DragAndDrop<Workspace>>()
-//                     .currently_dragged::<Channel>(cx.window())
-//                 {
-//                     if channel.id != dragged_channel.id {
-//                         this.drag_target_channel = ChannelDragTarget::Channel(channel.id);
-//                     }
-//                     cx.notify()
-//                 }
-//             }
-//         })
-//         .as_draggable::<_, Channel>(
-//             channel.clone(),
-//             move |_, channel, cx: &mut ViewContext<Workspace>| {
-//                 let theme = &theme::current(cx).collab_panel;
-
-//                 Flex::<Workspace>::row()
-//                     .with_child(
-//                         Svg::new("icons/hash.svg")
-//                             .with_color(theme.channel_hash.color)
-//                             .constrained()
-//                             .with_width(theme.channel_hash.width)
-//                             .aligned()
-//                             .left(),
-//                     )
-//                     .with_child(
-//                         Label::new(channel.name.clone(), theme.channel_name.text.clone())
-//                             .contained()
-//                             .with_style(theme.channel_name.container)
-//                             .aligned()
-//                             .left(),
-//                     )
-//                     .align_children_center()
-//                     .contained()
-//                     .with_background_color(
-//                         theme
-//                             .container
-//                             .background_color
-//                             .unwrap_or(gpui::color::Color::transparent_black()),
-//                     )
-//                     .contained()
-//                     .with_padding_left(
-//                         theme.channel_row.default_style().padding.left
-//                             + theme.channel_indent * depth as f32,
-//                     )
-//                     .into_any()
-//             },
-//         )
-//         .with_cursor_style(CursorStyle::PointingHand)
-//         .into_any()
-//     }
-
-//     fn render_channel_notes(
-//         &self,
-//         channel_id: ChannelId,
-//         theme: &theme::CollabPanel,
-//         is_selected: bool,
-//         ix: usize,
-//         cx: &mut ViewContext<Self>,
-//     ) -> AnyElement<Self> {
-//         enum ChannelNotes {}
-//         let host_avatar_width = theme
-//             .contact_avatar
-//             .width
-//             .or(theme.contact_avatar.height)
-//             .unwrap_or(0.);
-
-//         MouseEventHandler::new::<ChannelNotes, _>(ix as usize, cx, |state, cx| {
-//             let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
-//             let row = theme.project_row.in_state(is_selected).style_for(state);
-
-//             Flex::<Self>::row()
-//                 .with_child(render_tree_branch(
-//                     tree_branch,
-//                     &row.name.text,
-//                     false,
-//                     vec2f(host_avatar_width, theme.row_height),
-//                     cx.font_cache(),
-//                 ))
-//                 .with_child(
-//                     Svg::new("icons/file.svg")
-//                         .with_color(theme.channel_hash.color)
-//                         .constrained()
-//                         .with_width(theme.channel_hash.width)
-//                         .aligned()
-//                         .left(),
-//                 )
-//                 .with_child(
-//                     Label::new("notes", theme.channel_name.text.clone())
-//                         .contained()
-//                         .with_style(theme.channel_name.container)
-//                         .aligned()
-//                         .left()
-//                         .flex(1., true),
-//                 )
-//                 .constrained()
-//                 .with_height(theme.row_height)
-//                 .contained()
-//                 .with_style(*theme.channel_row.style_for(is_selected, state))
-//                 .with_padding_left(theme.channel_row.default_style().padding.left)
-//         })
-//         .on_click(MouseButton::Left, move |_, this, cx| {
-//             this.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
-//         })
-//         .with_cursor_style(CursorStyle::PointingHand)
-//         .into_any()
-//     }
-
-//     fn render_channel_chat(
-//         &self,
-//         channel_id: ChannelId,
-//         theme: &theme::CollabPanel,
-//         is_selected: bool,
-//         ix: usize,
-//         cx: &mut ViewContext<Self>,
-//     ) -> AnyElement<Self> {
-//         enum ChannelChat {}
-//         let host_avatar_width = theme
-//             .contact_avatar
-//             .width
-//             .or(theme.contact_avatar.height)
-//             .unwrap_or(0.);
-
-//         MouseEventHandler::new::<ChannelChat, _>(ix as usize, cx, |state, cx| {
-//             let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
-//             let row = theme.project_row.in_state(is_selected).style_for(state);
-
-//             Flex::<Self>::row()
-//                 .with_child(render_tree_branch(
-//                     tree_branch,
-//                     &row.name.text,
-//                     true,
-//                     vec2f(host_avatar_width, theme.row_height),
-//                     cx.font_cache(),
-//                 ))
-//                 .with_child(
-//                     Svg::new("icons/conversations.svg")
-//                         .with_color(theme.channel_hash.color)
-//                         .constrained()
-//                         .with_width(theme.channel_hash.width)
-//                         .aligned()
-//                         .left(),
-//                 )
-//                 .with_child(
-//                     Label::new("chat", theme.channel_name.text.clone())
-//                         .contained()
-//                         .with_style(theme.channel_name.container)
-//                         .aligned()
-//                         .left()
-//                         .flex(1., true),
-//                 )
-//                 .constrained()
-//                 .with_height(theme.row_height)
-//                 .contained()
-//                 .with_style(*theme.channel_row.style_for(is_selected, state))
-//                 .with_padding_left(theme.channel_row.default_style().padding.left)
-//         })
-//         .on_click(MouseButton::Left, move |_, this, cx| {
-//             this.join_channel_chat(&JoinChannelChat { channel_id }, cx);
-//         })
-//         .with_cursor_style(CursorStyle::PointingHand)
-//         .into_any()
-//     }
-
-//     fn render_channel_invite(
-//         channel: Arc<Channel>,
-//         channel_store: ModelHandle<ChannelStore>,
-//         theme: &theme::CollabPanel,
-//         is_selected: bool,
-//         cx: &mut ViewContext<Self>,
-//     ) -> AnyElement<Self> {
-//         enum Decline {}
-//         enum Accept {}
-
-//         let channel_id = channel.id;
-//         let is_invite_pending = channel_store
-//             .read(cx)
-//             .has_pending_channel_invite_response(&channel);
-//         let button_spacing = theme.contact_button_spacing;
-
-//         Flex::row()
-//             .with_child(
-//                 Svg::new("icons/hash.svg")
-//                     .with_color(theme.channel_hash.color)
-//                     .constrained()
-//                     .with_width(theme.channel_hash.width)
-//                     .aligned()
-//                     .left(),
-//             )
-//             .with_child(
-//                 Label::new(channel.name.clone(), theme.contact_username.text.clone())
-//                     .contained()
-//                     .with_style(theme.contact_username.container)
-//                     .aligned()
-//                     .left()
-//                     .flex(1., true),
-//             )
-//             .with_child(
-//                 MouseEventHandler::new::<Decline, _>(channel.id as usize, cx, |mouse_state, _| {
-//                     let button_style = if is_invite_pending {
-//                         &theme.disabled_button
-//                     } else {
-//                         theme.contact_button.style_for(mouse_state)
-//                     };
-//                     render_icon_button(button_style, "icons/x.svg").aligned()
-//                 })
-//                 .with_cursor_style(CursorStyle::PointingHand)
-//                 .on_click(MouseButton::Left, move |_, this, cx| {
-//                     this.respond_to_channel_invite(channel_id, false, cx);
-//                 })
-//                 .contained()
-//                 .with_margin_right(button_spacing),
-//             )
-//             .with_child(
-//                 MouseEventHandler::new::<Accept, _>(channel.id as usize, cx, |mouse_state, _| {
-//                     let button_style = if is_invite_pending {
-//                         &theme.disabled_button
-//                     } else {
-//                         theme.contact_button.style_for(mouse_state)
-//                     };
-//                     render_icon_button(button_style, "icons/check.svg")
-//                         .aligned()
-//                         .flex_float()
-//                 })
-//                 .with_cursor_style(CursorStyle::PointingHand)
-//                 .on_click(MouseButton::Left, move |_, this, cx| {
-//                     this.respond_to_channel_invite(channel_id, true, cx);
-//                 }),
-//             )
-//             .constrained()
-//             .with_height(theme.row_height)
-//             .contained()
-//             .with_style(
-//                 *theme
-//                     .contact_row
-//                     .in_state(is_selected)
-//                     .style_for(&mut Default::default()),
-//             )
-//             .with_padding_left(
-//                 theme.contact_row.default_style().padding.left + theme.channel_indent,
-//             )
-//             .into_any()
-//     }
-
-//     fn render_contact_request(
-//         user: Arc<User>,
-//         user_store: ModelHandle<UserStore>,
-//         theme: &theme::CollabPanel,
-//         is_incoming: bool,
-//         is_selected: bool,
-//         cx: &mut ViewContext<Self>,
-//     ) -> AnyElement<Self> {
-//         enum Decline {}
-//         enum Accept {}
-//         enum Cancel {}
-
-//         let mut row = Flex::row()
-//             .with_children(user.avatar.clone().map(|avatar| {
-//                 Image::from_data(avatar)
-//                     .with_style(theme.contact_avatar)
-//                     .aligned()
-//                     .left()
-//             }))
-//             .with_child(
-//                 Label::new(
-//                     user.github_login.clone(),
-//                     theme.contact_username.text.clone(),
-//                 )
-//                 .contained()
-//                 .with_style(theme.contact_username.container)
-//                 .aligned()
-//                 .left()
-//                 .flex(1., true),
-//             );
-
-//         let user_id = user.id;
-//         let github_login = user.github_login.clone();
-//         let is_contact_request_pending = user_store.read(cx).is_contact_request_pending(&user);
-//         let button_spacing = theme.contact_button_spacing;
-
-//         if is_incoming {
-//             row.add_child(
-//                 MouseEventHandler::new::<Decline, _>(user.id as usize, cx, |mouse_state, _| {
-//                     let button_style = if is_contact_request_pending {
-//                         &theme.disabled_button
-//                     } else {
-//                         theme.contact_button.style_for(mouse_state)
-//                     };
-//                     render_icon_button(button_style, "icons/x.svg").aligned()
-//                 })
-//                 .with_cursor_style(CursorStyle::PointingHand)
-//                 .on_click(MouseButton::Left, move |_, this, cx| {
-//                     this.respond_to_contact_request(user_id, false, cx);
-//                 })
-//                 .contained()
-//                 .with_margin_right(button_spacing),
-//             );
-
-//             row.add_child(
-//                 MouseEventHandler::new::<Accept, _>(user.id as usize, cx, |mouse_state, _| {
-//                     let button_style = if is_contact_request_pending {
-//                         &theme.disabled_button
-//                     } else {
-//                         theme.contact_button.style_for(mouse_state)
-//                     };
-//                     render_icon_button(button_style, "icons/check.svg")
-//                         .aligned()
-//                         .flex_float()
-//                 })
-//                 .with_cursor_style(CursorStyle::PointingHand)
-//                 .on_click(MouseButton::Left, move |_, this, cx| {
-//                     this.respond_to_contact_request(user_id, true, cx);
-//                 }),
-//             );
-//         } else {
-//             row.add_child(
-//                 MouseEventHandler::new::<Cancel, _>(user.id as usize, cx, |mouse_state, _| {
-//                     let button_style = if is_contact_request_pending {
-//                         &theme.disabled_button
-//                     } else {
-//                         theme.contact_button.style_for(mouse_state)
-//                     };
-//                     render_icon_button(button_style, "icons/x.svg")
-//                         .aligned()
-//                         .flex_float()
-//                 })
-//                 .with_padding(Padding::uniform(2.))
-//                 .with_cursor_style(CursorStyle::PointingHand)
-//                 .on_click(MouseButton::Left, move |_, this, cx| {
-//                     this.remove_contact(user_id, &github_login, cx);
-//                 })
-//                 .flex_float(),
-//             );
-//         }
-
-//         row.constrained()
-//             .with_height(theme.row_height)
-//             .contained()
-//             .with_style(
-//                 *theme
-//                     .contact_row
-//                     .in_state(is_selected)
-//                     .style_for(&mut Default::default()),
-//             )
-//             .into_any()
-//     }
-
-//     fn has_subchannels(&self, ix: usize) -> bool {
-//         self.entries.get(ix).map_or(false, |entry| {
-//             if let ListEntry::Channel { has_children, .. } = entry {
-//                 *has_children
-//             } else {
-//                 false
-//             }
-//         })
-//     }
-
-//     fn deploy_channel_context_menu(
-//         &mut self,
-//         position: Option<Vector2F>,
-//         channel: &Channel,
-//         ix: usize,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         self.context_menu_on_selected = position.is_none();
-
-//         let clipboard_channel_name = self.channel_clipboard.as_ref().and_then(|clipboard| {
-//             self.channel_store
-//                 .read(cx)
-//                 .channel_for_id(clipboard.channel_id)
-//                 .map(|channel| channel.name.clone())
-//         });
-
-//         self.context_menu.update(cx, |context_menu, cx| {
-//             context_menu.set_position_mode(if self.context_menu_on_selected {
-//                 OverlayPositionMode::Local
-//             } else {
-//                 OverlayPositionMode::Window
-//             });
-
-//             let mut items = Vec::new();
-
-//             let select_action_name = if self.selection == Some(ix) {
-//                 "Unselect"
-//             } else {
-//                 "Select"
-//             };
-
-//             items.push(ContextMenuItem::action(
-//                 select_action_name,
-//                 ToggleSelectedIx { ix },
-//             ));
-
-//             if self.has_subchannels(ix) {
-//                 let expand_action_name = if self.is_channel_collapsed(channel.id) {
-//                     "Expand Subchannels"
-//                 } else {
-//                     "Collapse Subchannels"
-//                 };
-//                 items.push(ContextMenuItem::action(
-//                     expand_action_name,
-//                     ToggleCollapse {
-//                         location: channel.id,
-//                     },
-//                 ));
-//             }
-
-//             items.push(ContextMenuItem::action(
-//                 "Open Notes",
-//                 OpenChannelNotes {
-//                     channel_id: channel.id,
-//                 },
-//             ));
-
-//             items.push(ContextMenuItem::action(
-//                 "Open Chat",
-//                 JoinChannelChat {
-//                     channel_id: channel.id,
-//                 },
-//             ));
-
-//             items.push(ContextMenuItem::action(
-//                 "Copy Channel Link",
-//                 CopyChannelLink {
-//                     channel_id: channel.id,
-//                 },
-//             ));
-
-//             if self.channel_store.read(cx).is_channel_admin(channel.id) {
-//                 items.extend([
-//                     ContextMenuItem::Separator,
-//                     ContextMenuItem::action(
-//                         "New Subchannel",
-//                         NewChannel {
-//                             location: channel.id,
-//                         },
-//                     ),
-//                     ContextMenuItem::action(
-//                         "Rename",
-//                         RenameChannel {
-//                             channel_id: channel.id,
-//                         },
-//                     ),
-//                     ContextMenuItem::action(
-//                         "Move this channel",
-//                         StartMoveChannelFor {
-//                             channel_id: channel.id,
-//                         },
-//                     ),
-//                 ]);
-
-//                 if let Some(channel_name) = clipboard_channel_name {
-//                     items.push(ContextMenuItem::Separator);
-//                     items.push(ContextMenuItem::action(
-//                         format!("Move '#{}' here", channel_name),
-//                         MoveChannel { to: channel.id },
-//                     ));
-//                 }
-
-//                 items.extend([
-//                     ContextMenuItem::Separator,
-//                     ContextMenuItem::action(
-//                         "Invite Members",
-//                         InviteMembers {
-//                             channel_id: channel.id,
-//                         },
-//                     ),
-//                     ContextMenuItem::action(
-//                         "Manage Members",
-//                         ManageMembers {
-//                             channel_id: channel.id,
-//                         },
-//                     ),
-//                     ContextMenuItem::Separator,
-//                     ContextMenuItem::action(
-//                         "Delete",
-//                         RemoveChannel {
-//                             channel_id: channel.id,
-//                         },
-//                     ),
-//                 ]);
-//             }
-
-//             context_menu.show(
-//                 position.unwrap_or_default(),
-//                 if self.context_menu_on_selected {
-//                     gpui::elements::AnchorCorner::TopRight
-//                 } else {
-//                     gpui::elements::AnchorCorner::BottomLeft
-//                 },
-//                 items,
-//                 cx,
-//             );
-//         });
-
-//         cx.notify();
-//     }
-
-//     fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
-//         if self.take_editing_state(cx) {
-//             cx.focus(&self.filter_editor);
-//         } else {
-//             self.filter_editor.update(cx, |editor, cx| {
-//                 if editor.buffer().read(cx).len(cx) > 0 {
-//                     editor.set_text("", cx);
-//                 }
-//             });
-//         }
-
-//         self.update_entries(false, cx);
-//     }
-
-//     fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
-//         let ix = self.selection.map_or(0, |ix| ix + 1);
-//         if ix < self.entries.len() {
-//             self.selection = Some(ix);
-//         }
-
-//         self.list_state.reset(self.entries.len());
-//         if let Some(ix) = self.selection {
-//             self.list_state.scroll_to(ListOffset {
-//                 item_ix: ix,
-//                 offset_in_item: 0.,
-//             });
-//         }
-//         cx.notify();
-//     }
-
-//     fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
-//         let ix = self.selection.take().unwrap_or(0);
-//         if ix > 0 {
-//             self.selection = Some(ix - 1);
-//         }
-
-//         self.list_state.reset(self.entries.len());
-//         if let Some(ix) = self.selection {
-//             self.list_state.scroll_to(ListOffset {
-//                 item_ix: ix,
-//                 offset_in_item: 0.,
-//             });
-//         }
-//         cx.notify();
-//     }
-
-//     fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
-//         if self.confirm_channel_edit(cx) {
-//             return;
-//         }
-
-//         if let Some(selection) = self.selection {
-//             if let Some(entry) = self.entries.get(selection) {
-//                 match entry {
-//                     ListEntry::Header(section) => match section {
-//                         Section::ActiveCall => Self::leave_call(cx),
-//                         Section::Channels => self.new_root_channel(cx),
-//                         Section::Contacts => self.toggle_contact_finder(cx),
-//                         Section::ContactRequests
-//                         | Section::Online
-//                         | Section::Offline
-//                         | Section::ChannelInvites => {
-//                             self.toggle_section_expanded(*section, cx);
-//                         }
-//                     },
-//                     ListEntry::Contact { contact, calling } => {
-//                         if contact.online && !contact.busy && !calling {
-//                             self.call(contact.user.id, Some(self.project.clone()), cx);
-//                         }
-//                     }
-//                     ListEntry::ParticipantProject {
-//                         project_id,
-//                         host_user_id,
-//                         ..
-//                     } => {
-//                         if let Some(workspace) = self.workspace.upgrade(cx) {
-//                             let app_state = workspace.read(cx).app_state().clone();
-//                             workspace::join_remote_project(
-//                                 *project_id,
-//                                 *host_user_id,
-//                                 app_state,
-//                                 cx,
-//                             )
-//                             .detach_and_log_err(cx);
-//                         }
-//                     }
-//                     ListEntry::ParticipantScreen { peer_id, .. } => {
-//                         let Some(peer_id) = peer_id else {
-//                             return;
-//                         };
-//                         if let Some(workspace) = self.workspace.upgrade(cx) {
-//                             workspace.update(cx, |workspace, cx| {
-//                                 workspace.open_shared_screen(*peer_id, cx)
-//                             });
-//                         }
-//                     }
-//                     ListEntry::Channel { channel, .. } => {
-//                         let is_active = maybe!({
-//                             let call_channel = ActiveCall::global(cx)
-//                                 .read(cx)
-//                                 .room()?
-//                                 .read(cx)
-//                                 .channel_id()?;
-
-//                             Some(call_channel == channel.id)
-//                         })
-//                         .unwrap_or(false);
-//                         if is_active {
-//                             self.open_channel_notes(
-//                                 &OpenChannelNotes {
-//                                     channel_id: channel.id,
-//                                 },
-//                                 cx,
-//                             )
-//                         } else {
-//                             self.join_channel(channel.id, cx)
-//                         }
-//                     }
-//                     ListEntry::ContactPlaceholder => self.toggle_contact_finder(cx),
-//                     _ => {}
-//                 }
-//             }
-//         }
-//     }
-
-//     fn insert_space(&mut self, _: &InsertSpace, cx: &mut ViewContext<Self>) {
-//         if self.channel_editing_state.is_some() {
-//             self.channel_name_editor.update(cx, |editor, cx| {
-//                 editor.insert(" ", cx);
-//             });
-//         }
-//     }
-
-//     fn confirm_channel_edit(&mut self, cx: &mut ViewContext<CollabPanel>) -> bool {
-//         if let Some(editing_state) = &mut self.channel_editing_state {
-//             match editing_state {
-//                 ChannelEditingState::Create {
-//                     location,
-//                     pending_name,
-//                     ..
-//                 } => {
-//                     if pending_name.is_some() {
-//                         return false;
-//                     }
-//                     let channel_name = self.channel_name_editor.read(cx).text(cx);
-
-//                     *pending_name = Some(channel_name.clone());
-
-//                     self.channel_store
-//                         .update(cx, |channel_store, cx| {
-//                             channel_store.create_channel(&channel_name, *location, cx)
-//                         })
-//                         .detach();
-//                     cx.notify();
-//                 }
-//                 ChannelEditingState::Rename {
-//                     location,
-//                     pending_name,
-//                 } => {
-//                     if pending_name.is_some() {
-//                         return false;
-//                     }
-//                     let channel_name = self.channel_name_editor.read(cx).text(cx);
-//                     *pending_name = Some(channel_name.clone());
-
-//                     self.channel_store
-//                         .update(cx, |channel_store, cx| {
-//                             channel_store.rename(*location, &channel_name, cx)
-//                         })
-//                         .detach();
-//                     cx.notify();
-//                 }
-//             }
-//             cx.focus_self();
-//             true
-//         } else {
-//             false
-//         }
-//     }
-
-//     fn toggle_section_expanded(&mut self, section: Section, cx: &mut ViewContext<Self>) {
-//         if let Some(ix) = self.collapsed_sections.iter().position(|s| *s == section) {
-//             self.collapsed_sections.remove(ix);
-//         } else {
-//             self.collapsed_sections.push(section);
-//         }
-//         self.update_entries(false, cx);
-//     }
-
-//     fn collapse_selected_channel(
-//         &mut self,
-//         _: &CollapseSelectedChannel,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         let Some(channel_id) = self.selected_channel().map(|channel| channel.id) else {
-//             return;
-//         };
-
-//         if self.is_channel_collapsed(channel_id) {
-//             return;
-//         }
-
-//         self.toggle_channel_collapsed(channel_id, cx);
-//     }
-
-//     fn expand_selected_channel(&mut self, _: &ExpandSelectedChannel, cx: &mut ViewContext<Self>) {
-//         let Some(id) = self.selected_channel().map(|channel| channel.id) else {
-//             return;
-//         };
-
-//         if !self.is_channel_collapsed(id) {
-//             return;
-//         }
-
-//         self.toggle_channel_collapsed(id, cx)
-//     }
-
-//     fn toggle_channel_collapsed_action(
-//         &mut self,
-//         action: &ToggleCollapse,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         self.toggle_channel_collapsed(action.location, cx);
-//     }
-
-//     fn toggle_channel_collapsed<'a>(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
-//         match self.collapsed_channels.binary_search(&channel_id) {
-//             Ok(ix) => {
-//                 self.collapsed_channels.remove(ix);
-//             }
-//             Err(ix) => {
-//                 self.collapsed_channels.insert(ix, channel_id);
-//             }
-//         };
-//         self.serialize(cx);
-//         self.update_entries(true, cx);
-//         cx.notify();
-//         cx.focus_self();
-//     }
-
-//     fn is_channel_collapsed(&self, channel_id: ChannelId) -> bool {
-//         self.collapsed_channels.binary_search(&channel_id).is_ok()
-//     }
-
-//     fn leave_call(cx: &mut ViewContext<Self>) {
-//         ActiveCall::global(cx)
-//             .update(cx, |call, cx| call.hang_up(cx))
-//             .detach_and_log_err(cx);
-//     }
-
-//     fn toggle_contact_finder(&mut self, cx: &mut ViewContext<Self>) {
-//         if let Some(workspace) = self.workspace.upgrade(cx) {
-//             workspace.update(cx, |workspace, cx| {
-//                 workspace.toggle_modal(cx, |_, cx| {
-//                     cx.add_view(|cx| {
-//                         let mut finder = ContactFinder::new(self.user_store.clone(), cx);
-//                         finder.set_query(self.filter_editor.read(cx).text(cx), cx);
-//                         finder
-//                     })
-//                 });
-//             });
-//         }
-//     }
+actions!(
+    ToggleFocus,
+    Remove,
+    Secondary,
+    CollapseSelectedChannel,
+    ExpandSelectedChannel,
+    StartMoveChannel,
+    MoveSelected,
+    InsertSpace,
+);
 
-//     fn new_root_channel(&mut self, cx: &mut ViewContext<Self>) {
-//         self.channel_editing_state = Some(ChannelEditingState::Create {
-//             location: None,
-//             pending_name: None,
-//         });
-//         self.update_entries(false, cx);
-//         self.select_channel_editor();
-//         cx.focus(self.channel_name_editor.as_any());
-//         cx.notify();
-//     }
-
-//     fn select_channel_editor(&mut self) {
-//         self.selection = self.entries.iter().position(|entry| match entry {
-//             ListEntry::ChannelEditor { .. } => true,
-//             _ => false,
-//         });
-//     }
+// impl_actions!(
+//     collab_panel,
+//     [
+//         RemoveChannel,
+//         NewChannel,
+//         InviteMembers,
+//         ManageMembers,
+//         RenameChannel,
+//         ToggleCollapse,
+//         OpenChannelNotes,
+//         JoinChannelCall,
+//         JoinChannelChat,
+//         CopyChannelLink,
+//         StartMoveChannelFor,
+//         MoveChannel,
+//         ToggleSelectedIx
+//     ]
+// );
 
-//     fn new_subchannel(&mut self, action: &NewChannel, cx: &mut ViewContext<Self>) {
-//         self.collapsed_channels
-//             .retain(|channel| *channel != action.location);
-//         self.channel_editing_state = Some(ChannelEditingState::Create {
-//             location: Some(action.location.to_owned()),
-//             pending_name: None,
-//         });
-//         self.update_entries(false, cx);
-//         self.select_channel_editor();
-//         cx.focus(self.channel_name_editor.as_any());
-//         cx.notify();
-//     }
+// #[derive(Debug, Copy, Clone, PartialEq, Eq)]
+// struct ChannelMoveClipboard {
+//     channel_id: ChannelId,
+// }
 
-//     fn invite_members(&mut self, action: &InviteMembers, cx: &mut ViewContext<Self>) {
-//         self.show_channel_modal(action.channel_id, channel_modal::Mode::InviteMembers, cx);
-//     }
+// const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel";
 
-//     fn manage_members(&mut self, action: &ManageMembers, cx: &mut ViewContext<Self>) {
-//         self.show_channel_modal(action.channel_id, channel_modal::Mode::ManageMembers, cx);
-//     }
+use std::sync::Arc;
+
+use gpui::{
+    actions, div, AppContext, AsyncWindowContext, Div, EventEmitter, FocusHandle, ParentComponent,
+    Render, Task, View, ViewContext, VisualContext, WeakView,
+};
+use project::Fs;
+use settings::Settings;
+use workspace::{
+    dock::{DockPosition, Panel, PanelEvent},
+    Workspace,
+};
+
+use crate::CollaborationPanelSettings;
+
+pub fn init(cx: &mut AppContext) {
+    //     contact_finder::init(cx);
+    //     channel_modal::init(cx);
+    //     channel_view::init(cx);
+
+    //     cx.add_action(CollabPanel::cancel);
+    //     cx.add_action(CollabPanel::select_next);
+    //     cx.add_action(CollabPanel::select_prev);
+    //     cx.add_action(CollabPanel::confirm);
+    //     cx.add_action(CollabPanel::insert_space);
+    //     cx.add_action(CollabPanel::remove);
+    //     cx.add_action(CollabPanel::remove_selected_channel);
+    //     cx.add_action(CollabPanel::show_inline_context_menu);
+    //     cx.add_action(CollabPanel::new_subchannel);
+    //     cx.add_action(CollabPanel::invite_members);
+    //     cx.add_action(CollabPanel::manage_members);
+    //     cx.add_action(CollabPanel::rename_selected_channel);
+    //     cx.add_action(CollabPanel::rename_channel);
+    //     cx.add_action(CollabPanel::toggle_channel_collapsed_action);
+    //     cx.add_action(CollabPanel::collapse_selected_channel);
+    //     cx.add_action(CollabPanel::expand_selected_channel);
+    //     cx.add_action(CollabPanel::open_channel_notes);
+    //     cx.add_action(CollabPanel::join_channel_chat);
+    //     cx.add_action(CollabPanel::copy_channel_link);
+
+    //     cx.add_action(
+    //         |panel: &mut CollabPanel, action: &ToggleSelectedIx, cx: &mut ViewContext<CollabPanel>| {
+    //             if panel.selection.take() != Some(action.ix) {
+    //                 panel.selection = Some(action.ix)
+    //             }
+
+    //             cx.notify();
+    //         },
+    //     );
+
+    //     cx.add_action(
+    //         |panel: &mut CollabPanel,
+    //          action: &StartMoveChannelFor,
+    //          _: &mut ViewContext<CollabPanel>| {
+    //             panel.channel_clipboard = Some(ChannelMoveClipboard {
+    //                 channel_id: action.channel_id,
+    //             });
+    //         },
+    //     );
+
+    //     cx.add_action(
+    //         |panel: &mut CollabPanel, _: &StartMoveChannel, _: &mut ViewContext<CollabPanel>| {
+    //             if let Some(channel) = panel.selected_channel() {
+    //                 panel.channel_clipboard = Some(ChannelMoveClipboard {
+    //                     channel_id: channel.id,
+    //                 })
+    //             }
+    //         },
+    //     );
+
+    //     cx.add_action(
+    //         |panel: &mut CollabPanel, _: &MoveSelected, cx: &mut ViewContext<CollabPanel>| {
+    //             let Some(clipboard) = panel.channel_clipboard.take() else {
+    //                 return;
+    //             };
+    //             let Some(selected_channel) = panel.selected_channel() else {
+    //                 return;
+    //             };
+
+    //             panel
+    //                 .channel_store
+    //                 .update(cx, |channel_store, cx| {
+    //                     channel_store.move_channel(clipboard.channel_id, Some(selected_channel.id), 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| {
+    //                     channel_store
+    //                         .move_channel(clipboard.channel_id, Some(action.to), cx)
+    //                         .detach_and_log_err(cx)
+    //                 })
+    //             }
+    //         },
+    //     );
+}
 
-//     fn remove(&mut self, _: &Remove, cx: &mut ViewContext<Self>) {
-//         if let Some(channel) = self.selected_channel() {
-//             self.remove_channel(channel.id, cx)
-//         }
-//     }
+// #[derive(Debug)]
+// pub enum ChannelEditingState {
+//     Create {
+//         location: Option<ChannelId>,
+//         pending_name: Option<String>,
+//     },
+//     Rename {
+//         location: ChannelId,
+//         pending_name: Option<String>,
+//     },
+// }
 
-//     fn rename_selected_channel(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
-//         if let Some(channel) = self.selected_channel() {
-//             self.rename_channel(
-//                 &RenameChannel {
-//                     channel_id: channel.id,
-//                 },
-//                 cx,
-//             );
+// impl ChannelEditingState {
+//     fn pending_name(&self) -> Option<&str> {
+//         match self {
+//             ChannelEditingState::Create { pending_name, .. } => pending_name.as_deref(),
+//             ChannelEditingState::Rename { pending_name, .. } => pending_name.as_deref(),
 //         }
 //     }
+// }
 
-//     fn rename_channel(&mut self, action: &RenameChannel, cx: &mut ViewContext<Self>) {
-//         let channel_store = self.channel_store.read(cx);
-//         if !channel_store.is_channel_admin(action.channel_id) {
-//             return;
-//         }
-//         if let Some(channel) = channel_store.channel_for_id(action.channel_id).cloned() {
-//             self.channel_editing_state = Some(ChannelEditingState::Rename {
-//                 location: action.channel_id.to_owned(),
-//                 pending_name: None,
-//             });
-//             self.channel_name_editor.update(cx, |editor, cx| {
-//                 editor.set_text(channel.name.clone(), cx);
-//                 editor.select_all(&Default::default(), cx);
-//             });
-//             cx.focus(self.channel_name_editor.as_any());
-//             self.update_entries(false, cx);
-//             self.select_channel_editor();
-//         }
-//     }
+pub struct CollabPanel {
+    width: Option<f32>,
+    fs: Arc<dyn Fs>,
+    focus_handle: FocusHandle,
+    // channel_clipboard: Option<ChannelMoveClipboard>,
+    // pending_serialization: Task<Option<()>>,
+    // context_menu: ViewHandle<ContextMenu>,
+    // filter_editor: ViewHandle<Editor>,
+    // channel_name_editor: ViewHandle<Editor>,
+    // channel_editing_state: Option<ChannelEditingState>,
+    // entries: Vec<ListEntry>,
+    // selection: Option<usize>,
+    // user_store: ModelHandle<UserStore>,
+    // client: Arc<Client>,
+    // channel_store: ModelHandle<ChannelStore>,
+    // project: ModelHandle<Project>,
+    // match_candidates: Vec<StringMatchCandidate>,
+    // list_state: ListState<Self>,
+    // subscriptions: Vec<Subscription>,
+    // collapsed_sections: Vec<Section>,
+    // collapsed_channels: Vec<ChannelId>,
+    // drag_target_channel: ChannelDragTarget,
+    _workspace: WeakView<Workspace>,
+    // context_menu_on_selected: bool,
+}
 
-//     fn open_channel_notes(&mut self, action: &OpenChannelNotes, cx: &mut ViewContext<Self>) {
-//         if let Some(workspace) = self.workspace.upgrade(cx) {
-//             ChannelView::open(action.channel_id, workspace, cx).detach();
-//         }
-//     }
+// #[derive(PartialEq, Eq)]
+// enum ChannelDragTarget {
+//     None,
+//     Root,
+//     Channel(ChannelId),
+// }
 
-//     fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext<Self>) {
-//         let Some(channel) = self.selected_channel() else {
-//             return;
-//         };
+// #[derive(Serialize, Deserialize)]
+// struct SerializedCollabPanel {
+//     width: Option<f32>,
+//     collapsed_channels: Option<Vec<ChannelId>>,
+// }
 
-//         self.deploy_channel_context_menu(None, &channel.clone(), self.selection.unwrap(), cx);
-//     }
+// #[derive(Debug)]
+// pub enum Event {
+//     DockPositionChanged,
+//     Focus,
+//     Dismissed,
+// }
 
-//     fn selected_channel(&self) -> Option<&Arc<Channel>> {
-//         self.selection
-//             .and_then(|ix| self.entries.get(ix))
-//             .and_then(|entry| match entry {
-//                 ListEntry::Channel { channel, .. } => Some(channel),
-//                 _ => None,
-//             })
-//     }
+// #[derive(Clone, Copy, PartialEq, Eq, Debug, PartialOrd, Ord)]
+// enum Section {
+//     ActiveCall,
+//     Channels,
+//     ChannelInvites,
+//     ContactRequests,
+//     Contacts,
+//     Online,
+//     Offline,
+// }
 
-//     fn show_channel_modal(
-//         &mut self,
+// #[derive(Clone, Debug)]
+// enum ListEntry {
+//     Header(Section),
+//     CallParticipant {
+//         user: Arc<User>,
+//         peer_id: Option<PeerId>,
+//         is_pending: bool,
+//     },
+//     ParticipantProject {
+//         project_id: u64,
+//         worktree_root_names: Vec<String>,
+//         host_user_id: u64,
+//         is_last: bool,
+//     },
+//     ParticipantScreen {
+//         peer_id: Option<PeerId>,
+//         is_last: bool,
+//     },
+//     IncomingRequest(Arc<User>),
+//     OutgoingRequest(Arc<User>),
+//     ChannelInvite(Arc<Channel>),
+//     Channel {
+//         channel: Arc<Channel>,
+//         depth: usize,
+//         has_children: bool,
+//     },
+//     ChannelNotes {
 //         channel_id: ChannelId,
-//         mode: channel_modal::Mode,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         let workspace = self.workspace.clone();
-//         let user_store = self.user_store.clone();
-//         let channel_store = self.channel_store.clone();
-//         let members = self.channel_store.update(cx, |channel_store, cx| {
-//             channel_store.get_channel_member_details(channel_id, cx)
-//         });
-
-//         cx.spawn(|_, mut cx| async move {
-//             let members = members.await?;
-//             workspace.update(&mut cx, |workspace, cx| {
-//                 workspace.toggle_modal(cx, |_, cx| {
-//                     cx.add_view(|cx| {
-//                         ChannelModal::new(
-//                             user_store.clone(),
-//                             channel_store.clone(),
-//                             channel_id,
-//                             mode,
-//                             members,
-//                             cx,
-//                         )
-//                     })
-//                 });
-//             })
-//         })
-//         .detach();
-//     }
-
-//     fn remove_selected_channel(&mut self, action: &RemoveChannel, cx: &mut ViewContext<Self>) {
-//         self.remove_channel(action.channel_id, cx)
-//     }
-
-//     fn remove_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
-//         let channel_store = self.channel_store.clone();
-//         if let Some(channel) = channel_store.read(cx).channel_for_id(channel_id) {
-//             let prompt_message = format!(
-//                 "Are you sure you want to remove the channel \"{}\"?",
-//                 channel.name
-//             );
-//             let mut answer =
-//                 cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
-//             let window = cx.window();
-//             cx.spawn(|this, mut cx| async move {
-//                 if answer.next().await == Some(0) {
-//                     if let Err(e) = channel_store
-//                         .update(&mut cx, |channels, _| channels.remove_channel(channel_id))
-//                         .await
-//                     {
-//                         window.prompt(
-//                             PromptLevel::Info,
-//                             &format!("Failed to remove channel: {}", e),
-//                             &["Ok"],
-//                             &mut cx,
-//                         );
-//                     }
-//                     this.update(&mut cx, |_, cx| cx.focus_self()).ok();
-//                 }
-//             })
-//             .detach();
-//         }
-//     }
-
-//     // Should move to the filter editor if clicking on it
-//     // Should move selection to the channel editor if activating it
-
-//     fn remove_contact(&mut self, user_id: u64, github_login: &str, cx: &mut ViewContext<Self>) {
-//         let user_store = self.user_store.clone();
-//         let prompt_message = format!(
-//             "Are you sure you want to remove \"{}\" from your contacts?",
-//             github_login
-//         );
-//         let mut answer = cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
-//         let window = cx.window();
-//         cx.spawn(|_, mut cx| async move {
-//             if answer.next().await == Some(0) {
-//                 if let Err(e) = user_store
-//                     .update(&mut cx, |store, cx| store.remove_contact(user_id, cx))
-//                     .await
-//                 {
-//                     window.prompt(
-//                         PromptLevel::Info,
-//                         &format!("Failed to remove contact: {}", e),
-//                         &["Ok"],
-//                         &mut cx,
-//                     );
-//                 }
-//             }
-//         })
-//         .detach();
-//     }
-
-//     fn respond_to_contact_request(
-//         &mut self,
-//         user_id: u64,
-//         accept: bool,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         self.user_store
-//             .update(cx, |store, cx| {
-//                 store.respond_to_contact_request(user_id, accept, cx)
-//             })
-//             .detach();
-//     }
-
-//     fn respond_to_channel_invite(
-//         &mut self,
-//         channel_id: u64,
-//         accept: bool,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         self.channel_store
-//             .update(cx, |store, cx| {
-//                 store.respond_to_channel_invite(channel_id, accept, cx)
-//             })
-//             .detach();
-//     }
-
-//     fn call(
-//         &mut self,
-//         recipient_user_id: u64,
-//         initial_project: Option<ModelHandle<Project>>,
-//         cx: &mut ViewContext<Self>,
-//     ) {
-//         ActiveCall::global(cx)
-//             .update(cx, |call, cx| {
-//                 call.invite(recipient_user_id, initial_project, cx)
-//             })
-//             .detach_and_log_err(cx);
-//     }
-
-//     fn join_channel(&self, channel_id: u64, cx: &mut ViewContext<Self>) {
-//         let Some(workspace) = self.workspace.upgrade(cx) else {
-//             return;
-//         };
-//         let Some(handle) = cx.window().downcast::<Workspace>() else {
-//             return;
-//         };
-//         workspace::join_channel(
-//             channel_id,
-//             workspace.read(cx).app_state().clone(),
-//             Some(handle),
-//             cx,
-//         )
-//         .detach_and_log_err(cx)
-//     }
-
-//     fn join_channel_chat(&mut self, action: &JoinChannelChat, cx: &mut ViewContext<Self>) {
-//         let channel_id = action.channel_id;
-//         if let Some(workspace) = self.workspace.upgrade(cx) {
-//             cx.app_context().defer(move |cx| {
-//                 workspace.update(cx, |workspace, cx| {
-//                     if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
-//                         panel.update(cx, |panel, cx| {
-//                             panel
-//                                 .select_channel(channel_id, None, cx)
-//                                 .detach_and_log_err(cx);
-//                         });
-//                     }
-//                 });
-//             });
-//         }
-//     }
+//     },
+//     ChannelChat {
+//         channel_id: ChannelId,
+//     },
+//     ChannelEditor {
+//         depth: usize,
+//     },
+//     Contact {
+//         contact: Arc<Contact>,
+//         calling: bool,
+//     },
+//     ContactPlaceholder,
+// }
 
-//     fn copy_channel_link(&mut self, action: &CopyChannelLink, cx: &mut ViewContext<Self>) {
-//         let channel_store = self.channel_store.read(cx);
-//         let Some(channel) = channel_store.channel_for_id(action.channel_id) else {
-//             return;
-//         };
-//         let item = ClipboardItem::new(channel.link());
-//         cx.write_to_clipboard(item)
-//     }
+// impl Entity for CollabPanel {
+//     type Event = Event;
 // }
 
+impl CollabPanel {
+    pub fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
+        cx.build_view(|cx| {
+            //             let view_id = cx.view_id();
+
+            //             let filter_editor = cx.add_view(|cx| {
+            //                 let mut editor = Editor::single_line(
+            //                     Some(Arc::new(|theme| {
+            //                         theme.collab_panel.user_query_editor.clone()
+            //                     })),
+            //                     cx,
+            //                 );
+            //                 editor.set_placeholder_text("Filter channels, contacts", cx);
+            //                 editor
+            //             });
+
+            //             cx.subscribe(&filter_editor, |this, _, event, cx| {
+            //                 if let editor::Event::BufferEdited = event {
+            //                     let query = this.filter_editor.read(cx).text(cx);
+            //                     if !query.is_empty() {
+            //                         this.selection.take();
+            //                     }
+            //                     this.update_entries(true, cx);
+            //                     if !query.is_empty() {
+            //                         this.selection = this
+            //                             .entries
+            //                             .iter()
+            //                             .position(|entry| !matches!(entry, ListEntry::Header(_)));
+            //                     }
+            //                 } else if let editor::Event::Blurred = event {
+            //                     let query = this.filter_editor.read(cx).text(cx);
+            //                     if query.is_empty() {
+            //                         this.selection.take();
+            //                         this.update_entries(true, cx);
+            //                     }
+            //                 }
+            //             })
+            //             .detach();
+
+            //             let channel_name_editor = cx.add_view(|cx| {
+            //                 Editor::single_line(
+            //                     Some(Arc::new(|theme| {
+            //                         theme.collab_panel.user_query_editor.clone()
+            //                     })),
+            //                     cx,
+            //                 )
+            //             });
+
+            //             cx.subscribe(&channel_name_editor, |this, _, event, cx| {
+            //                 if let editor::Event::Blurred = event {
+            //                     if let Some(state) = &this.channel_editing_state {
+            //                         if state.pending_name().is_some() {
+            //                             return;
+            //                         }
+            //                     }
+            //                     this.take_editing_state(cx);
+            //                     this.update_entries(false, cx);
+            //                     cx.notify();
+            //                 }
+            //             })
+            //             .detach();
+
+            //             let list_state =
+            //                 ListState::<Self>::new(0, Orientation::Top, 1000., move |this, ix, cx| {
+            //                     let theme = theme::current(cx).clone();
+            //                     let is_selected = this.selection == Some(ix);
+            //                     let current_project_id = this.project.read(cx).remote_id();
+
+            //                     match &this.entries[ix] {
+            //                         ListEntry::Header(section) => {
+            //                             let is_collapsed = this.collapsed_sections.contains(section);
+            //                             this.render_header(*section, &theme, is_selected, is_collapsed, cx)
+            //                         }
+            //                         ListEntry::CallParticipant {
+            //                             user,
+            //                             peer_id,
+            //                             is_pending,
+            //                         } => Self::render_call_participant(
+            //                             user,
+            //                             *peer_id,
+            //                             this.user_store.clone(),
+            //                             *is_pending,
+            //                             is_selected,
+            //                             &theme,
+            //                             cx,
+            //                         ),
+            //                         ListEntry::ParticipantProject {
+            //                             project_id,
+            //                             worktree_root_names,
+            //                             host_user_id,
+            //                             is_last,
+            //                         } => Self::render_participant_project(
+            //                             *project_id,
+            //                             worktree_root_names,
+            //                             *host_user_id,
+            //                             Some(*project_id) == current_project_id,
+            //                             *is_last,
+            //                             is_selected,
+            //                             &theme,
+            //                             cx,
+            //                         ),
+            //                         ListEntry::ParticipantScreen { peer_id, is_last } => {
+            //                             Self::render_participant_screen(
+            //                                 *peer_id,
+            //                                 *is_last,
+            //                                 is_selected,
+            //                                 &theme.collab_panel,
+            //                                 cx,
+            //                             )
+            //                         }
+            //                         ListEntry::Channel {
+            //                             channel,
+            //                             depth,
+            //                             has_children,
+            //                         } => {
+            //                             let channel_row = this.render_channel(
+            //                                 &*channel,
+            //                                 *depth,
+            //                                 &theme,
+            //                                 is_selected,
+            //                                 *has_children,
+            //                                 ix,
+            //                                 cx,
+            //                             );
+
+            //                             if is_selected && this.context_menu_on_selected {
+            //                                 Stack::new()
+            //                                     .with_child(channel_row)
+            //                                     .with_child(
+            //                                         ChildView::new(&this.context_menu, cx)
+            //                                             .aligned()
+            //                                             .bottom()
+            //                                             .right(),
+            //                                     )
+            //                                     .into_any()
+            //                             } else {
+            //                                 return channel_row;
+            //                             }
+            //                         }
+            //                         ListEntry::ChannelNotes { channel_id } => this.render_channel_notes(
+            //                             *channel_id,
+            //                             &theme.collab_panel,
+            //                             is_selected,
+            //                             ix,
+            //                             cx,
+            //                         ),
+            //                         ListEntry::ChannelChat { channel_id } => this.render_channel_chat(
+            //                             *channel_id,
+            //                             &theme.collab_panel,
+            //                             is_selected,
+            //                             ix,
+            //                             cx,
+            //                         ),
+            //                         ListEntry::ChannelInvite(channel) => Self::render_channel_invite(
+            //                             channel.clone(),
+            //                             this.channel_store.clone(),
+            //                             &theme.collab_panel,
+            //                             is_selected,
+            //                             cx,
+            //                         ),
+            //                         ListEntry::IncomingRequest(user) => Self::render_contact_request(
+            //                             user.clone(),
+            //                             this.user_store.clone(),
+            //                             &theme.collab_panel,
+            //                             true,
+            //                             is_selected,
+            //                             cx,
+            //                         ),
+            //                         ListEntry::OutgoingRequest(user) => Self::render_contact_request(
+            //                             user.clone(),
+            //                             this.user_store.clone(),
+            //                             &theme.collab_panel,
+            //                             false,
+            //                             is_selected,
+            //                             cx,
+            //                         ),
+            //                         ListEntry::Contact { contact, calling } => Self::render_contact(
+            //                             contact,
+            //                             *calling,
+            //                             &this.project,
+            //                             &theme,
+            //                             is_selected,
+            //                             cx,
+            //                         ),
+            //                         ListEntry::ChannelEditor { depth } => {
+            //                             this.render_channel_editor(&theme, *depth, cx)
+            //                         }
+            //                         ListEntry::ContactPlaceholder => {
+            //                             this.render_contact_placeholder(&theme.collab_panel, is_selected, cx)
+            //                         }
+            //                     }
+            //                 });
+
+            let this = Self {
+                width: None,
+                focus_handle: cx.focus_handle(),
+                //                 channel_clipboard: None,
+                fs: workspace.app_state().fs.clone(),
+                //                 pending_serialization: Task::ready(None),
+                //                 context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)),
+                //                 channel_name_editor,
+                //                 filter_editor,
+                //                 entries: Vec::default(),
+                //                 channel_editing_state: None,
+                //                 selection: None,
+                //                 user_store: workspace.user_store().clone(),
+                //                 channel_store: ChannelStore::global(cx),
+                //                 project: workspace.project().clone(),
+                //                 subscriptions: Vec::default(),
+                //                 match_candidates: Vec::default(),
+                //                 collapsed_sections: vec![Section::Offline],
+                //                 collapsed_channels: Vec::default(),
+                _workspace: workspace.weak_handle(),
+                //                 client: workspace.app_state().client.clone(),
+                //                 context_menu_on_selected: true,
+                //                 drag_target_channel: ChannelDragTarget::None,
+                //                 list_state,
+            };
+
+            //             this.update_entries(false, cx);
+
+            //             // Update the dock position when the setting changes.
+            //             let mut old_dock_position = this.position(cx);
+            //             this.subscriptions
+            //                 .push(
+            //                     cx.observe_global::<SettingsStore, _>(move |this: &mut Self, cx| {
+            //                         let new_dock_position = this.position(cx);
+            //                         if new_dock_position != old_dock_position {
+            //                             old_dock_position = new_dock_position;
+            //                             cx.emit(Event::DockPositionChanged);
+            //                         }
+            //                         cx.notify();
+            //                     }),
+            //                 );
+
+            //             let active_call = ActiveCall::global(cx);
+            //             this.subscriptions
+            //                 .push(cx.observe(&this.user_store, |this, _, cx| {
+            //                     this.update_entries(true, cx)
+            //                 }));
+            //             this.subscriptions
+            //                 .push(cx.observe(&this.channel_store, |this, _, cx| {
+            //                     this.update_entries(true, cx)
+            //                 }));
+            //             this.subscriptions
+            //                 .push(cx.observe(&active_call, |this, _, cx| this.update_entries(true, cx)));
+            //             this.subscriptions
+            //                 .push(cx.observe_flag::<ChannelsAlpha, _>(move |_, this, cx| {
+            //                     this.update_entries(true, cx)
+            //                 }));
+            //             this.subscriptions.push(cx.subscribe(
+            //                 &this.channel_store,
+            //                 |this, _channel_store, e, cx| match e {
+            //                     ChannelEvent::ChannelCreated(channel_id)
+            //                     | ChannelEvent::ChannelRenamed(channel_id) => {
+            //                         if this.take_editing_state(cx) {
+            //                             this.update_entries(false, cx);
+            //                             this.selection = this.entries.iter().position(|entry| {
+            //                                 if let ListEntry::Channel { channel, .. } = entry {
+            //                                     channel.id == *channel_id
+            //                                 } else {
+            //                                     false
+            //                                 }
+            //                             });
+            //                         }
+            //                     }
+            //                 },
+            //             ));
+
+            this
+        })
+    }
+
+    pub fn load(
+        workspace: WeakView<Workspace>,
+        cx: AsyncWindowContext,
+    ) -> Task<anyhow::Result<View<Self>>> {
+        cx.spawn(|mut cx| async move {
+            // todo!()
+            // let serialized_panel = if let Some(panel) = cx
+            //     .background()
+            //     .spawn(async move { KEY_VALUE_STORE.read_kvp(COLLABORATION_PANEL_KEY) })
+            //     .await
+            //     .log_err()
+            //     .flatten()
+            // {
+            //     match serde_json::from_str::<SerializedCollabPanel>(&panel) {
+            //         Ok(panel) => Some(panel),
+            //         Err(err) => {
+            //             log::error!("Failed to deserialize collaboration panel: {}", err);
+            //             None
+            //         }
+            //     }
+            // } else {
+            //     None
+            // };
+
+            workspace.update(&mut cx, |workspace, cx| {
+                let panel = CollabPanel::new(workspace, cx);
+                // if let Some(serialized_panel) = serialized_panel {
+                //     panel.update(cx, |panel, cx| {
+                //         panel.width = serialized_panel.width;
+                //         panel.collapsed_channels = serialized_panel
+                //             .collapsed_channels
+                //             .unwrap_or_else(|| Vec::new());
+                //         cx.notify();
+                //     });
+                // }
+                panel
+            })
+        })
+    }
+
+    //     fn serialize(&mut self, cx: &mut ViewContext<Self>) {
+    //         let width = self.width;
+    //         let collapsed_channels = self.collapsed_channels.clone();
+    //         self.pending_serialization = cx.background().spawn(
+    //             async move {
+    //                 KEY_VALUE_STORE
+    //                     .write_kvp(
+    //                         COLLABORATION_PANEL_KEY.into(),
+    //                         serde_json::to_string(&SerializedCollabPanel {
+    //                             width,
+    //                             collapsed_channels: Some(collapsed_channels),
+    //                         })?,
+    //                     )
+    //                     .await?;
+    //                 anyhow::Ok(())
+    //             }
+    //             .log_err(),
+    //         );
+    //     }
+
+    //     fn update_entries(&mut self, select_same_item: bool, cx: &mut ViewContext<Self>) {
+    //         let channel_store = self.channel_store.read(cx);
+    //         let user_store = self.user_store.read(cx);
+    //         let query = self.filter_editor.read(cx).text(cx);
+    //         let executor = cx.background().clone();
+
+    //         let prev_selected_entry = self.selection.and_then(|ix| self.entries.get(ix).cloned());
+    //         let old_entries = mem::take(&mut self.entries);
+    //         let mut scroll_to_top = false;
+
+    //         if let Some(room) = ActiveCall::global(cx).read(cx).room() {
+    //             self.entries.push(ListEntry::Header(Section::ActiveCall));
+    //             if !old_entries
+    //                 .iter()
+    //                 .any(|entry| matches!(entry, ListEntry::Header(Section::ActiveCall)))
+    //             {
+    //                 scroll_to_top = true;
+    //             }
+
+    //             if !self.collapsed_sections.contains(&Section::ActiveCall) {
+    //                 let room = room.read(cx);
+
+    //                 if let Some(channel_id) = room.channel_id() {
+    //                     self.entries.push(ListEntry::ChannelNotes { channel_id });
+    //                     self.entries.push(ListEntry::ChannelChat { channel_id })
+    //                 }
+
+    //                 // Populate the active user.
+    //                 if let Some(user) = user_store.current_user() {
+    //                     self.match_candidates.clear();
+    //                     self.match_candidates.push(StringMatchCandidate {
+    //                         id: 0,
+    //                         string: user.github_login.clone(),
+    //                         char_bag: user.github_login.chars().collect(),
+    //                     });
+    //                     let matches = executor.block(match_strings(
+    //                         &self.match_candidates,
+    //                         &query,
+    //                         true,
+    //                         usize::MAX,
+    //                         &Default::default(),
+    //                         executor.clone(),
+    //                     ));
+    //                     if !matches.is_empty() {
+    //                         let user_id = user.id;
+    //                         self.entries.push(ListEntry::CallParticipant {
+    //                             user,
+    //                             peer_id: None,
+    //                             is_pending: false,
+    //                         });
+    //                         let mut projects = room.local_participant().projects.iter().peekable();
+    //                         while let Some(project) = projects.next() {
+    //                             self.entries.push(ListEntry::ParticipantProject {
+    //                                 project_id: project.id,
+    //                                 worktree_root_names: project.worktree_root_names.clone(),
+    //                                 host_user_id: user_id,
+    //                                 is_last: projects.peek().is_none() && !room.is_screen_sharing(),
+    //                             });
+    //                         }
+    //                         if room.is_screen_sharing() {
+    //                             self.entries.push(ListEntry::ParticipantScreen {
+    //                                 peer_id: None,
+    //                                 is_last: true,
+    //                             });
+    //                         }
+    //                     }
+    //                 }
+
+    //                 // Populate remote participants.
+    //                 self.match_candidates.clear();
+    //                 self.match_candidates
+    //                     .extend(room.remote_participants().iter().map(|(_, participant)| {
+    //                         StringMatchCandidate {
+    //                             id: participant.user.id as usize,
+    //                             string: participant.user.github_login.clone(),
+    //                             char_bag: participant.user.github_login.chars().collect(),
+    //                         }
+    //                     }));
+    //                 let matches = executor.block(match_strings(
+    //                     &self.match_candidates,
+    //                     &query,
+    //                     true,
+    //                     usize::MAX,
+    //                     &Default::default(),
+    //                     executor.clone(),
+    //                 ));
+    //                 for mat in matches {
+    //                     let user_id = mat.candidate_id as u64;
+    //                     let participant = &room.remote_participants()[&user_id];
+    //                     self.entries.push(ListEntry::CallParticipant {
+    //                         user: participant.user.clone(),
+    //                         peer_id: Some(participant.peer_id),
+    //                         is_pending: false,
+    //                     });
+    //                     let mut projects = participant.projects.iter().peekable();
+    //                     while let Some(project) = projects.next() {
+    //                         self.entries.push(ListEntry::ParticipantProject {
+    //                             project_id: project.id,
+    //                             worktree_root_names: project.worktree_root_names.clone(),
+    //                             host_user_id: participant.user.id,
+    //                             is_last: projects.peek().is_none()
+    //                                 && participant.video_tracks.is_empty(),
+    //                         });
+    //                     }
+    //                     if !participant.video_tracks.is_empty() {
+    //                         self.entries.push(ListEntry::ParticipantScreen {
+    //                             peer_id: Some(participant.peer_id),
+    //                             is_last: true,
+    //                         });
+    //                     }
+    //                 }
+
+    //                 // Populate pending participants.
+    //                 self.match_candidates.clear();
+    //                 self.match_candidates
+    //                     .extend(room.pending_participants().iter().enumerate().map(
+    //                         |(id, participant)| StringMatchCandidate {
+    //                             id,
+    //                             string: participant.github_login.clone(),
+    //                             char_bag: participant.github_login.chars().collect(),
+    //                         },
+    //                     ));
+    //                 let matches = executor.block(match_strings(
+    //                     &self.match_candidates,
+    //                     &query,
+    //                     true,
+    //                     usize::MAX,
+    //                     &Default::default(),
+    //                     executor.clone(),
+    //                 ));
+    //                 self.entries
+    //                     .extend(matches.iter().map(|mat| ListEntry::CallParticipant {
+    //                         user: room.pending_participants()[mat.candidate_id].clone(),
+    //                         peer_id: None,
+    //                         is_pending: true,
+    //                     }));
+    //             }
+    //         }
+
+    //         let mut request_entries = Vec::new();
+
+    //         if cx.has_flag::<ChannelsAlpha>() {
+    //             self.entries.push(ListEntry::Header(Section::Channels));
+
+    //             if channel_store.channel_count() > 0 || self.channel_editing_state.is_some() {
+    //                 self.match_candidates.clear();
+    //                 self.match_candidates
+    //                     .extend(channel_store.ordered_channels().enumerate().map(
+    //                         |(ix, (_, channel))| StringMatchCandidate {
+    //                             id: ix,
+    //                             string: channel.name.clone(),
+    //                             char_bag: channel.name.chars().collect(),
+    //                         },
+    //                     ));
+    //                 let matches = executor.block(match_strings(
+    //                     &self.match_candidates,
+    //                     &query,
+    //                     true,
+    //                     usize::MAX,
+    //                     &Default::default(),
+    //                     executor.clone(),
+    //                 ));
+    //                 if let Some(state) = &self.channel_editing_state {
+    //                     if matches!(state, ChannelEditingState::Create { location: None, .. }) {
+    //                         self.entries.push(ListEntry::ChannelEditor { depth: 0 });
+    //                     }
+    //                 }
+    //                 let mut collapse_depth = None;
+    //                 for mat in matches {
+    //                     let channel = channel_store.channel_at_index(mat.candidate_id).unwrap();
+    //                     let depth = channel.parent_path.len();
+
+    //                     if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) {
+    //                         collapse_depth = Some(depth);
+    //                     } else if let Some(collapsed_depth) = collapse_depth {
+    //                         if depth > collapsed_depth {
+    //                             continue;
+    //                         }
+    //                         if self.is_channel_collapsed(channel.id) {
+    //                             collapse_depth = Some(depth);
+    //                         } else {
+    //                             collapse_depth = None;
+    //                         }
+    //                     }
+
+    //                     let has_children = channel_store
+    //                         .channel_at_index(mat.candidate_id + 1)
+    //                         .map_or(false, |next_channel| {
+    //                             next_channel.parent_path.ends_with(&[channel.id])
+    //                         });
+
+    //                     match &self.channel_editing_state {
+    //                         Some(ChannelEditingState::Create {
+    //                             location: parent_id,
+    //                             ..
+    //                         }) if *parent_id == Some(channel.id) => {
+    //                             self.entries.push(ListEntry::Channel {
+    //                                 channel: channel.clone(),
+    //                                 depth,
+    //                                 has_children: false,
+    //                             });
+    //                             self.entries
+    //                                 .push(ListEntry::ChannelEditor { depth: depth + 1 });
+    //                         }
+    //                         Some(ChannelEditingState::Rename {
+    //                             location: parent_id,
+    //                             ..
+    //                         }) if parent_id == &channel.id => {
+    //                             self.entries.push(ListEntry::ChannelEditor { depth });
+    //                         }
+    //                         _ => {
+    //                             self.entries.push(ListEntry::Channel {
+    //                                 channel: channel.clone(),
+    //                                 depth,
+    //                                 has_children,
+    //                             });
+    //                         }
+    //                     }
+    //                 }
+    //             }
+
+    //             let channel_invites = channel_store.channel_invitations();
+    //             if !channel_invites.is_empty() {
+    //                 self.match_candidates.clear();
+    //                 self.match_candidates
+    //                     .extend(channel_invites.iter().enumerate().map(|(ix, channel)| {
+    //                         StringMatchCandidate {
+    //                             id: ix,
+    //                             string: channel.name.clone(),
+    //                             char_bag: channel.name.chars().collect(),
+    //                         }
+    //                     }));
+    //                 let matches = executor.block(match_strings(
+    //                     &self.match_candidates,
+    //                     &query,
+    //                     true,
+    //                     usize::MAX,
+    //                     &Default::default(),
+    //                     executor.clone(),
+    //                 ));
+    //                 request_entries.extend(matches.iter().map(|mat| {
+    //                     ListEntry::ChannelInvite(channel_invites[mat.candidate_id].clone())
+    //                 }));
+
+    //                 if !request_entries.is_empty() {
+    //                     self.entries
+    //                         .push(ListEntry::Header(Section::ChannelInvites));
+    //                     if !self.collapsed_sections.contains(&Section::ChannelInvites) {
+    //                         self.entries.append(&mut request_entries);
+    //                     }
+    //                 }
+    //             }
+    //         }
+
+    //         self.entries.push(ListEntry::Header(Section::Contacts));
+
+    //         request_entries.clear();
+    //         let incoming = user_store.incoming_contact_requests();
+    //         if !incoming.is_empty() {
+    //             self.match_candidates.clear();
+    //             self.match_candidates
+    //                 .extend(
+    //                     incoming
+    //                         .iter()
+    //                         .enumerate()
+    //                         .map(|(ix, user)| StringMatchCandidate {
+    //                             id: ix,
+    //                             string: user.github_login.clone(),
+    //                             char_bag: user.github_login.chars().collect(),
+    //                         }),
+    //                 );
+    //             let matches = executor.block(match_strings(
+    //                 &self.match_candidates,
+    //                 &query,
+    //                 true,
+    //                 usize::MAX,
+    //                 &Default::default(),
+    //                 executor.clone(),
+    //             ));
+    //             request_entries.extend(
+    //                 matches
+    //                     .iter()
+    //                     .map(|mat| ListEntry::IncomingRequest(incoming[mat.candidate_id].clone())),
+    //             );
+    //         }
+
+    //         let outgoing = user_store.outgoing_contact_requests();
+    //         if !outgoing.is_empty() {
+    //             self.match_candidates.clear();
+    //             self.match_candidates
+    //                 .extend(
+    //                     outgoing
+    //                         .iter()
+    //                         .enumerate()
+    //                         .map(|(ix, user)| StringMatchCandidate {
+    //                             id: ix,
+    //                             string: user.github_login.clone(),
+    //                             char_bag: user.github_login.chars().collect(),
+    //                         }),
+    //                 );
+    //             let matches = executor.block(match_strings(
+    //                 &self.match_candidates,
+    //                 &query,
+    //                 true,
+    //                 usize::MAX,
+    //                 &Default::default(),
+    //                 executor.clone(),
+    //             ));
+    //             request_entries.extend(
+    //                 matches
+    //                     .iter()
+    //                     .map(|mat| ListEntry::OutgoingRequest(outgoing[mat.candidate_id].clone())),
+    //             );
+    //         }
+
+    //         if !request_entries.is_empty() {
+    //             self.entries
+    //                 .push(ListEntry::Header(Section::ContactRequests));
+    //             if !self.collapsed_sections.contains(&Section::ContactRequests) {
+    //                 self.entries.append(&mut request_entries);
+    //             }
+    //         }
+
+    //         let contacts = user_store.contacts();
+    //         if !contacts.is_empty() {
+    //             self.match_candidates.clear();
+    //             self.match_candidates
+    //                 .extend(
+    //                     contacts
+    //                         .iter()
+    //                         .enumerate()
+    //                         .map(|(ix, contact)| StringMatchCandidate {
+    //                             id: ix,
+    //                             string: contact.user.github_login.clone(),
+    //                             char_bag: contact.user.github_login.chars().collect(),
+    //                         }),
+    //                 );
+
+    //             let matches = executor.block(match_strings(
+    //                 &self.match_candidates,
+    //                 &query,
+    //                 true,
+    //                 usize::MAX,
+    //                 &Default::default(),
+    //                 executor.clone(),
+    //             ));
+
+    //             let (online_contacts, offline_contacts) = matches
+    //                 .iter()
+    //                 .partition::<Vec<_>, _>(|mat| contacts[mat.candidate_id].online);
+
+    //             for (matches, section) in [
+    //                 (online_contacts, Section::Online),
+    //                 (offline_contacts, Section::Offline),
+    //             ] {
+    //                 if !matches.is_empty() {
+    //                     self.entries.push(ListEntry::Header(section));
+    //                     if !self.collapsed_sections.contains(&section) {
+    //                         let active_call = &ActiveCall::global(cx).read(cx);
+    //                         for mat in matches {
+    //                             let contact = &contacts[mat.candidate_id];
+    //                             self.entries.push(ListEntry::Contact {
+    //                                 contact: contact.clone(),
+    //                                 calling: active_call.pending_invites().contains(&contact.user.id),
+    //                             });
+    //                         }
+    //                     }
+    //                 }
+    //             }
+    //         }
+
+    //         if incoming.is_empty() && outgoing.is_empty() && contacts.is_empty() {
+    //             self.entries.push(ListEntry::ContactPlaceholder);
+    //         }
+
+    //         if select_same_item {
+    //             if let Some(prev_selected_entry) = prev_selected_entry {
+    //                 self.selection.take();
+    //                 for (ix, entry) in self.entries.iter().enumerate() {
+    //                     if *entry == prev_selected_entry {
+    //                         self.selection = Some(ix);
+    //                         break;
+    //                     }
+    //                 }
+    //             }
+    //         } else {
+    //             self.selection = self.selection.and_then(|prev_selection| {
+    //                 if self.entries.is_empty() {
+    //                     None
+    //                 } else {
+    //                     Some(prev_selection.min(self.entries.len() - 1))
+    //                 }
+    //             });
+    //         }
+
+    //         let old_scroll_top = self.list_state.logical_scroll_top();
+
+    //         self.list_state.reset(self.entries.len());
+
+    //         if scroll_to_top {
+    //             self.list_state.scroll_to(ListOffset::default());
+    //         } else {
+    //             // Attempt to maintain the same scroll position.
+    //             if let Some(old_top_entry) = old_entries.get(old_scroll_top.item_ix) {
+    //                 let new_scroll_top = self
+    //                     .entries
+    //                     .iter()
+    //                     .position(|entry| entry == old_top_entry)
+    //                     .map(|item_ix| ListOffset {
+    //                         item_ix,
+    //                         offset_in_item: old_scroll_top.offset_in_item,
+    //                     })
+    //                     .or_else(|| {
+    //                         let entry_after_old_top = old_entries.get(old_scroll_top.item_ix + 1)?;
+    //                         let item_ix = self
+    //                             .entries
+    //                             .iter()
+    //                             .position(|entry| entry == entry_after_old_top)?;
+    //                         Some(ListOffset {
+    //                             item_ix,
+    //                             offset_in_item: 0.,
+    //                         })
+    //                     })
+    //                     .or_else(|| {
+    //                         let entry_before_old_top =
+    //                             old_entries.get(old_scroll_top.item_ix.saturating_sub(1))?;
+    //                         let item_ix = self
+    //                             .entries
+    //                             .iter()
+    //                             .position(|entry| entry == entry_before_old_top)?;
+    //                         Some(ListOffset {
+    //                             item_ix,
+    //                             offset_in_item: 0.,
+    //                         })
+    //                     });
+
+    //                 self.list_state
+    //                     .scroll_to(new_scroll_top.unwrap_or(old_scroll_top));
+    //             }
+    //         }
+
+    //         cx.notify();
+    //     }
+
+    //     fn render_call_participant(
+    //         user: &User,
+    //         peer_id: Option<PeerId>,
+    //         user_store: ModelHandle<UserStore>,
+    //         is_pending: bool,
+    //         is_selected: bool,
+    //         theme: &theme::Theme,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> AnyElement<Self> {
+    //         enum CallParticipant {}
+    //         enum CallParticipantTooltip {}
+    //         enum LeaveCallButton {}
+    //         enum LeaveCallTooltip {}
+
+    //         let collab_theme = &theme.collab_panel;
+
+    //         let is_current_user =
+    //             user_store.read(cx).current_user().map(|user| user.id) == Some(user.id);
+
+    //         let content = MouseEventHandler::new::<CallParticipant, _>(
+    //             user.id as usize,
+    //             cx,
+    //             |mouse_state, cx| {
+    //                 let style = if is_current_user {
+    //                     *collab_theme
+    //                         .contact_row
+    //                         .in_state(is_selected)
+    //                         .style_for(&mut Default::default())
+    //                 } else {
+    //                     *collab_theme
+    //                         .contact_row
+    //                         .in_state(is_selected)
+    //                         .style_for(mouse_state)
+    //                 };
+
+    //                 Flex::row()
+    //                     .with_children(user.avatar.clone().map(|avatar| {
+    //                         Image::from_data(avatar)
+    //                             .with_style(collab_theme.contact_avatar)
+    //                             .aligned()
+    //                             .left()
+    //                     }))
+    //                     .with_child(
+    //                         Label::new(
+    //                             user.github_login.clone(),
+    //                             collab_theme.contact_username.text.clone(),
+    //                         )
+    //                         .contained()
+    //                         .with_style(collab_theme.contact_username.container)
+    //                         .aligned()
+    //                         .left()
+    //                         .flex(1., true),
+    //                     )
+    //                     .with_children(if is_pending {
+    //                         Some(
+    //                             Label::new("Calling", collab_theme.calling_indicator.text.clone())
+    //                                 .contained()
+    //                                 .with_style(collab_theme.calling_indicator.container)
+    //                                 .aligned()
+    //                                 .into_any(),
+    //                         )
+    //                     } else if is_current_user {
+    //                         Some(
+    //                             MouseEventHandler::new::<LeaveCallButton, _>(0, cx, |state, _| {
+    //                                 render_icon_button(
+    //                                     theme
+    //                                         .collab_panel
+    //                                         .leave_call_button
+    //                                         .style_for(is_selected, state),
+    //                                     "icons/exit.svg",
+    //                                 )
+    //                             })
+    //                             .with_cursor_style(CursorStyle::PointingHand)
+    //                             .on_click(MouseButton::Left, |_, _, cx| {
+    //                                 Self::leave_call(cx);
+    //                             })
+    //                             .with_tooltip::<LeaveCallTooltip>(
+    //                                 0,
+    //                                 "Leave call",
+    //                                 None,
+    //                                 theme.tooltip.clone(),
+    //                                 cx,
+    //                             )
+    //                             .into_any(),
+    //                         )
+    //                     } else {
+    //                         None
+    //                     })
+    //                     .constrained()
+    //                     .with_height(collab_theme.row_height)
+    //                     .contained()
+    //                     .with_style(style)
+    //             },
+    //         );
+
+    //         if is_current_user || is_pending || peer_id.is_none() {
+    //             return content.into_any();
+    //         }
+
+    //         let tooltip = format!("Follow {}", user.github_login);
+
+    //         content
+    //             .on_click(MouseButton::Left, move |_, this, cx| {
+    //                 if let Some(workspace) = this.workspace.upgrade(cx) {
+    //                     workspace
+    //                         .update(cx, |workspace, cx| workspace.follow(peer_id.unwrap(), cx))
+    //                         .map(|task| task.detach_and_log_err(cx));
+    //                 }
+    //             })
+    //             .with_cursor_style(CursorStyle::PointingHand)
+    //             .with_tooltip::<CallParticipantTooltip>(
+    //                 user.id as usize,
+    //                 tooltip,
+    //                 Some(Box::new(FollowNextCollaborator)),
+    //                 theme.tooltip.clone(),
+    //                 cx,
+    //             )
+    //             .into_any()
+    //     }
+
+    //     fn render_participant_project(
+    //         project_id: u64,
+    //         worktree_root_names: &[String],
+    //         host_user_id: u64,
+    //         is_current: bool,
+    //         is_last: bool,
+    //         is_selected: bool,
+    //         theme: &theme::Theme,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> AnyElement<Self> {
+    //         enum JoinProject {}
+    //         enum JoinProjectTooltip {}
+
+    //         let collab_theme = &theme.collab_panel;
+    //         let host_avatar_width = collab_theme
+    //             .contact_avatar
+    //             .width
+    //             .or(collab_theme.contact_avatar.height)
+    //             .unwrap_or(0.);
+    //         let tree_branch = collab_theme.tree_branch;
+    //         let project_name = if worktree_root_names.is_empty() {
+    //             "untitled".to_string()
+    //         } else {
+    //             worktree_root_names.join(", ")
+    //         };
+
+    //         let content =
+    //             MouseEventHandler::new::<JoinProject, _>(project_id as usize, cx, |mouse_state, cx| {
+    //                 let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
+    //                 let row = if is_current {
+    //                     collab_theme
+    //                         .project_row
+    //                         .in_state(true)
+    //                         .style_for(&mut Default::default())
+    //                 } else {
+    //                     collab_theme
+    //                         .project_row
+    //                         .in_state(is_selected)
+    //                         .style_for(mouse_state)
+    //                 };
+
+    //                 Flex::row()
+    //                     .with_child(render_tree_branch(
+    //                         tree_branch,
+    //                         &row.name.text,
+    //                         is_last,
+    //                         vec2f(host_avatar_width, collab_theme.row_height),
+    //                         cx.font_cache(),
+    //                     ))
+    //                     .with_child(
+    //                         Svg::new("icons/file_icons/folder.svg")
+    //                             .with_color(collab_theme.channel_hash.color)
+    //                             .constrained()
+    //                             .with_width(collab_theme.channel_hash.width)
+    //                             .aligned()
+    //                             .left(),
+    //                     )
+    //                     .with_child(
+    //                         Label::new(project_name.clone(), row.name.text.clone())
+    //                             .aligned()
+    //                             .left()
+    //                             .contained()
+    //                             .with_style(row.name.container)
+    //                             .flex(1., false),
+    //                     )
+    //                     .constrained()
+    //                     .with_height(collab_theme.row_height)
+    //                     .contained()
+    //                     .with_style(row.container)
+    //             });
+
+    //         if is_current {
+    //             return content.into_any();
+    //         }
+
+    //         content
+    //             .with_cursor_style(CursorStyle::PointingHand)
+    //             .on_click(MouseButton::Left, move |_, this, cx| {
+    //                 if let Some(workspace) = this.workspace.upgrade(cx) {
+    //                     let app_state = workspace.read(cx).app_state().clone();
+    //                     workspace::join_remote_project(project_id, host_user_id, app_state, cx)
+    //                         .detach_and_log_err(cx);
+    //                 }
+    //             })
+    //             .with_tooltip::<JoinProjectTooltip>(
+    //                 project_id as usize,
+    //                 format!("Open {}", project_name),
+    //                 None,
+    //                 theme.tooltip.clone(),
+    //                 cx,
+    //             )
+    //             .into_any()
+    //     }
+
+    //     fn render_participant_screen(
+    //         peer_id: Option<PeerId>,
+    //         is_last: bool,
+    //         is_selected: bool,
+    //         theme: &theme::CollabPanel,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> AnyElement<Self> {
+    //         enum OpenSharedScreen {}
+
+    //         let host_avatar_width = theme
+    //             .contact_avatar
+    //             .width
+    //             .or(theme.contact_avatar.height)
+    //             .unwrap_or(0.);
+    //         let tree_branch = theme.tree_branch;
+
+    //         let handler = MouseEventHandler::new::<OpenSharedScreen, _>(
+    //             peer_id.map(|id| id.as_u64()).unwrap_or(0) as usize,
+    //             cx,
+    //             |mouse_state, cx| {
+    //                 let tree_branch = *tree_branch.in_state(is_selected).style_for(mouse_state);
+    //                 let row = theme
+    //                     .project_row
+    //                     .in_state(is_selected)
+    //                     .style_for(mouse_state);
+
+    //                 Flex::row()
+    //                     .with_child(render_tree_branch(
+    //                         tree_branch,
+    //                         &row.name.text,
+    //                         is_last,
+    //                         vec2f(host_avatar_width, theme.row_height),
+    //                         cx.font_cache(),
+    //                     ))
+    //                     .with_child(
+    //                         Svg::new("icons/desktop.svg")
+    //                             .with_color(theme.channel_hash.color)
+    //                             .constrained()
+    //                             .with_width(theme.channel_hash.width)
+    //                             .aligned()
+    //                             .left(),
+    //                     )
+    //                     .with_child(
+    //                         Label::new("Screen", row.name.text.clone())
+    //                             .aligned()
+    //                             .left()
+    //                             .contained()
+    //                             .with_style(row.name.container)
+    //                             .flex(1., false),
+    //                     )
+    //                     .constrained()
+    //                     .with_height(theme.row_height)
+    //                     .contained()
+    //                     .with_style(row.container)
+    //             },
+    //         );
+    //         if peer_id.is_none() {
+    //             return handler.into_any();
+    //         }
+    //         handler
+    //             .with_cursor_style(CursorStyle::PointingHand)
+    //             .on_click(MouseButton::Left, move |_, this, cx| {
+    //                 if let Some(workspace) = this.workspace.upgrade(cx) {
+    //                     workspace.update(cx, |workspace, cx| {
+    //                         workspace.open_shared_screen(peer_id.unwrap(), cx)
+    //                     });
+    //                 }
+    //             })
+    //             .into_any()
+    //     }
+
+    //     fn take_editing_state(&mut self, cx: &mut ViewContext<Self>) -> bool {
+    //         if let Some(_) = self.channel_editing_state.take() {
+    //             self.channel_name_editor.update(cx, |editor, cx| {
+    //                 editor.set_text("", cx);
+    //             });
+    //             true
+    //         } else {
+    //             false
+    //         }
+    //     }
+
+    //     fn render_header(
+    //         &self,
+    //         section: Section,
+    //         theme: &theme::Theme,
+    //         is_selected: bool,
+    //         is_collapsed: bool,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> AnyElement<Self> {
+    //         enum Header {}
+    //         enum LeaveCallContactList {}
+    //         enum AddChannel {}
+
+    //         let tooltip_style = &theme.tooltip;
+    //         let mut channel_link = None;
+    //         let mut channel_tooltip_text = None;
+    //         let mut channel_icon = None;
+    //         let mut is_dragged_over = false;
+
+    //         let text = match section {
+    //             Section::ActiveCall => {
+    //                 let channel_name = maybe!({
+    //                     let channel_id = ActiveCall::global(cx).read(cx).channel_id(cx)?;
+
+    //                     let channel = self.channel_store.read(cx).channel_for_id(channel_id)?;
+
+    //                     channel_link = Some(channel.link());
+    //                     (channel_icon, channel_tooltip_text) = match channel.visibility {
+    //                         proto::ChannelVisibility::Public => {
+    //                             (Some("icons/public.svg"), Some("Copy public channel link."))
+    //                         }
+    //                         proto::ChannelVisibility::Members => {
+    //                             (Some("icons/hash.svg"), Some("Copy private channel link."))
+    //                         }
+    //                     };
+
+    //                     Some(channel.name.as_str())
+    //                 });
+
+    //                 if let Some(name) = channel_name {
+    //                     Cow::Owned(format!("{}", name))
+    //                 } else {
+    //                     Cow::Borrowed("Current Call")
+    //                 }
+    //             }
+    //             Section::ContactRequests => Cow::Borrowed("Requests"),
+    //             Section::Contacts => Cow::Borrowed("Contacts"),
+    //             Section::Channels => Cow::Borrowed("Channels"),
+    //             Section::ChannelInvites => Cow::Borrowed("Invites"),
+    //             Section::Online => Cow::Borrowed("Online"),
+    //             Section::Offline => Cow::Borrowed("Offline"),
+    //         };
+
+    //         enum AddContact {}
+    //         let button = match section {
+    //             Section::ActiveCall => channel_link.map(|channel_link| {
+    //                 let channel_link_copy = channel_link.clone();
+    //                 MouseEventHandler::new::<AddContact, _>(0, cx, |state, _| {
+    //                     render_icon_button(
+    //                         theme
+    //                             .collab_panel
+    //                             .leave_call_button
+    //                             .style_for(is_selected, state),
+    //                         "icons/link.svg",
+    //                     )
+    //                 })
+    //                 .with_cursor_style(CursorStyle::PointingHand)
+    //                 .on_click(MouseButton::Left, move |_, _, cx| {
+    //                     let item = ClipboardItem::new(channel_link_copy.clone());
+    //                     cx.write_to_clipboard(item)
+    //                 })
+    //                 .with_tooltip::<AddContact>(
+    //                     0,
+    //                     channel_tooltip_text.unwrap(),
+    //                     None,
+    //                     tooltip_style.clone(),
+    //                     cx,
+    //                 )
+    //             }),
+    //             Section::Contacts => Some(
+    //                 MouseEventHandler::new::<LeaveCallContactList, _>(0, cx, |state, _| {
+    //                     render_icon_button(
+    //                         theme
+    //                             .collab_panel
+    //                             .add_contact_button
+    //                             .style_for(is_selected, state),
+    //                         "icons/plus.svg",
+    //                     )
+    //                 })
+    //                 .with_cursor_style(CursorStyle::PointingHand)
+    //                 .on_click(MouseButton::Left, |_, this, cx| {
+    //                     this.toggle_contact_finder(cx);
+    //                 })
+    //                 .with_tooltip::<LeaveCallContactList>(
+    //                     0,
+    //                     "Search for new contact",
+    //                     None,
+    //                     tooltip_style.clone(),
+    //                     cx,
+    //                 ),
+    //             ),
+    //             Section::Channels => {
+    //                 if cx
+    //                     .global::<DragAndDrop<Workspace>>()
+    //                     .currently_dragged::<Channel>(cx.window())
+    //                     .is_some()
+    //                     && self.drag_target_channel == ChannelDragTarget::Root
+    //                 {
+    //                     is_dragged_over = true;
+    //                 }
+
+    //                 Some(
+    //                     MouseEventHandler::new::<AddChannel, _>(0, cx, |state, _| {
+    //                         render_icon_button(
+    //                             theme
+    //                                 .collab_panel
+    //                                 .add_contact_button
+    //                                 .style_for(is_selected, state),
+    //                             "icons/plus.svg",
+    //                         )
+    //                     })
+    //                     .with_cursor_style(CursorStyle::PointingHand)
+    //                     .on_click(MouseButton::Left, |_, this, cx| this.new_root_channel(cx))
+    //                     .with_tooltip::<AddChannel>(
+    //                         0,
+    //                         "Create a channel",
+    //                         None,
+    //                         tooltip_style.clone(),
+    //                         cx,
+    //                     ),
+    //                 )
+    //             }
+    //             _ => None,
+    //         };
+
+    //         let can_collapse = match section {
+    //             Section::ActiveCall | Section::Channels | Section::Contacts => false,
+    //             Section::ChannelInvites
+    //             | Section::ContactRequests
+    //             | Section::Online
+    //             | Section::Offline => true,
+    //         };
+    //         let icon_size = (&theme.collab_panel).section_icon_size;
+    //         let mut result = MouseEventHandler::new::<Header, _>(section as usize, cx, |state, _| {
+    //             let header_style = if can_collapse {
+    //                 theme
+    //                     .collab_panel
+    //                     .subheader_row
+    //                     .in_state(is_selected)
+    //                     .style_for(state)
+    //             } else {
+    //                 &theme.collab_panel.header_row
+    //             };
+
+    //             Flex::row()
+    //                 .with_children(if can_collapse {
+    //                     Some(
+    //                         Svg::new(if is_collapsed {
+    //                             "icons/chevron_right.svg"
+    //                         } else {
+    //                             "icons/chevron_down.svg"
+    //                         })
+    //                         .with_color(header_style.text.color)
+    //                         .constrained()
+    //                         .with_max_width(icon_size)
+    //                         .with_max_height(icon_size)
+    //                         .aligned()
+    //                         .constrained()
+    //                         .with_width(icon_size)
+    //                         .contained()
+    //                         .with_margin_right(
+    //                             theme.collab_panel.contact_username.container.margin.left,
+    //                         ),
+    //                     )
+    //                 } else if let Some(channel_icon) = channel_icon {
+    //                     Some(
+    //                         Svg::new(channel_icon)
+    //                             .with_color(header_style.text.color)
+    //                             .constrained()
+    //                             .with_max_width(icon_size)
+    //                             .with_max_height(icon_size)
+    //                             .aligned()
+    //                             .constrained()
+    //                             .with_width(icon_size)
+    //                             .contained()
+    //                             .with_margin_right(
+    //                                 theme.collab_panel.contact_username.container.margin.left,
+    //                             ),
+    //                     )
+    //                 } else {
+    //                     None
+    //                 })
+    //                 .with_child(
+    //                     Label::new(text, header_style.text.clone())
+    //                         .aligned()
+    //                         .left()
+    //                         .flex(1., true),
+    //                 )
+    //                 .with_children(button.map(|button| button.aligned().right()))
+    //                 .constrained()
+    //                 .with_height(theme.collab_panel.row_height)
+    //                 .contained()
+    //                 .with_style(if is_dragged_over {
+    //                     theme.collab_panel.dragged_over_header
+    //                 } else {
+    //                     header_style.container
+    //                 })
+    //         });
+
+    //         result = result
+    //             .on_move(move |_, this, cx| {
+    //                 if cx
+    //                     .global::<DragAndDrop<Workspace>>()
+    //                     .currently_dragged::<Channel>(cx.window())
+    //                     .is_some()
+    //                 {
+    //                     this.drag_target_channel = ChannelDragTarget::Root;
+    //                     cx.notify()
+    //                 }
+    //             })
+    //             .on_up(MouseButton::Left, move |_, this, cx| {
+    //                 if let Some((_, dragged_channel)) = cx
+    //                     .global::<DragAndDrop<Workspace>>()
+    //                     .currently_dragged::<Channel>(cx.window())
+    //                 {
+    //                     this.channel_store
+    //                         .update(cx, |channel_store, cx| {
+    //                             channel_store.move_channel(dragged_channel.id, None, cx)
+    //                         })
+    //                         .detach_and_log_err(cx)
+    //                 }
+    //             });
+
+    //         if can_collapse {
+    //             result = result
+    //                 .with_cursor_style(CursorStyle::PointingHand)
+    //                 .on_click(MouseButton::Left, move |_, this, cx| {
+    //                     if can_collapse {
+    //                         this.toggle_section_expanded(section, cx);
+    //                     }
+    //                 })
+    //         }
+
+    //         result.into_any()
+    //     }
+
+    //     fn render_contact(
+    //         contact: &Contact,
+    //         calling: bool,
+    //         project: &ModelHandle<Project>,
+    //         theme: &theme::Theme,
+    //         is_selected: bool,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> AnyElement<Self> {
+    //         enum ContactTooltip {}
+
+    //         let collab_theme = &theme.collab_panel;
+    //         let online = contact.online;
+    //         let busy = contact.busy || calling;
+    //         let user_id = contact.user.id;
+    //         let github_login = contact.user.github_login.clone();
+    //         let initial_project = project.clone();
+
+    //         let event_handler =
+    //             MouseEventHandler::new::<Contact, _>(contact.user.id as usize, cx, |state, cx| {
+    //                 Flex::row()
+    //                     .with_children(contact.user.avatar.clone().map(|avatar| {
+    //                         let status_badge = if contact.online {
+    //                             Some(
+    //                                 Empty::new()
+    //                                     .collapsed()
+    //                                     .contained()
+    //                                     .with_style(if busy {
+    //                                         collab_theme.contact_status_busy
+    //                                     } else {
+    //                                         collab_theme.contact_status_free
+    //                                     })
+    //                                     .aligned(),
+    //                             )
+    //                         } else {
+    //                             None
+    //                         };
+    //                         Stack::new()
+    //                             .with_child(
+    //                                 Image::from_data(avatar)
+    //                                     .with_style(collab_theme.contact_avatar)
+    //                                     .aligned()
+    //                                     .left(),
+    //                             )
+    //                             .with_children(status_badge)
+    //                     }))
+    //                     .with_child(
+    //                         Label::new(
+    //                             contact.user.github_login.clone(),
+    //                             collab_theme.contact_username.text.clone(),
+    //                         )
+    //                         .contained()
+    //                         .with_style(collab_theme.contact_username.container)
+    //                         .aligned()
+    //                         .left()
+    //                         .flex(1., true),
+    //                     )
+    //                     .with_children(if state.hovered() {
+    //                         Some(
+    //                             MouseEventHandler::new::<Cancel, _>(
+    //                                 contact.user.id as usize,
+    //                                 cx,
+    //                                 |mouse_state, _| {
+    //                                     let button_style =
+    //                                         collab_theme.contact_button.style_for(mouse_state);
+    //                                     render_icon_button(button_style, "icons/x.svg")
+    //                                         .aligned()
+    //                                         .flex_float()
+    //                                 },
+    //                             )
+    //                             .with_padding(Padding::uniform(2.))
+    //                             .with_cursor_style(CursorStyle::PointingHand)
+    //                             .on_click(MouseButton::Left, move |_, this, cx| {
+    //                                 this.remove_contact(user_id, &github_login, cx);
+    //                             })
+    //                             .flex_float(),
+    //                         )
+    //                     } else {
+    //                         None
+    //                     })
+    //                     .with_children(if calling {
+    //                         Some(
+    //                             Label::new("Calling", collab_theme.calling_indicator.text.clone())
+    //                                 .contained()
+    //                                 .with_style(collab_theme.calling_indicator.container)
+    //                                 .aligned(),
+    //                         )
+    //                     } else {
+    //                         None
+    //                     })
+    //                     .constrained()
+    //                     .with_height(collab_theme.row_height)
+    //                     .contained()
+    //                     .with_style(
+    //                         *collab_theme
+    //                             .contact_row
+    //                             .in_state(is_selected)
+    //                             .style_for(state),
+    //                     )
+    //             });
+
+    //         if online && !busy {
+    //             let room = ActiveCall::global(cx).read(cx).room();
+    //             let label = if room.is_some() {
+    //                 format!("Invite {} to join call", contact.user.github_login)
+    //             } else {
+    //                 format!("Call {}", contact.user.github_login)
+    //             };
+
+    //             event_handler
+    //                 .on_click(MouseButton::Left, move |_, this, cx| {
+    //                     this.call(user_id, Some(initial_project.clone()), cx);
+    //                 })
+    //                 .with_cursor_style(CursorStyle::PointingHand)
+    //                 .with_tooltip::<ContactTooltip>(
+    //                     contact.user.id as usize,
+    //                     label,
+    //                     None,
+    //                     theme.tooltip.clone(),
+    //                     cx,
+    //                 )
+    //                 .into_any()
+    //         } else {
+    //             event_handler
+    //                 .with_tooltip::<ContactTooltip>(
+    //                     contact.user.id as usize,
+    //                     format!(
+    //                         "{} is {}",
+    //                         contact.user.github_login,
+    //                         if busy { "on a call" } else { "offline" }
+    //                     ),
+    //                     None,
+    //                     theme.tooltip.clone(),
+    //                     cx,
+    //                 )
+    //                 .into_any()
+    //         }
+    //     }
+
+    //     fn render_contact_placeholder(
+    //         &self,
+    //         theme: &theme::CollabPanel,
+    //         is_selected: bool,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> AnyElement<Self> {
+    //         enum AddContacts {}
+    //         MouseEventHandler::new::<AddContacts, _>(0, cx, |state, _| {
+    //             let style = theme.list_empty_state.style_for(is_selected, state);
+    //             Flex::row()
+    //                 .with_child(
+    //                     Svg::new("icons/plus.svg")
+    //                         .with_color(theme.list_empty_icon.color)
+    //                         .constrained()
+    //                         .with_width(theme.list_empty_icon.width)
+    //                         .aligned()
+    //                         .left(),
+    //                 )
+    //                 .with_child(
+    //                     Label::new("Add a contact", style.text.clone())
+    //                         .contained()
+    //                         .with_style(theme.list_empty_label_container),
+    //                 )
+    //                 .align_children_center()
+    //                 .contained()
+    //                 .with_style(style.container)
+    //                 .into_any()
+    //         })
+    //         .on_click(MouseButton::Left, |_, this, cx| {
+    //             this.toggle_contact_finder(cx);
+    //         })
+    //         .into_any()
+    //     }
+
+    //     fn render_channel_editor(
+    //         &self,
+    //         theme: &theme::Theme,
+    //         depth: usize,
+    //         cx: &AppContext,
+    //     ) -> AnyElement<Self> {
+    //         Flex::row()
+    //             .with_child(
+    //                 Empty::new()
+    //                     .constrained()
+    //                     .with_width(theme.collab_panel.disclosure.button_space()),
+    //             )
+    //             .with_child(
+    //                 Svg::new("icons/hash.svg")
+    //                     .with_color(theme.collab_panel.channel_hash.color)
+    //                     .constrained()
+    //                     .with_width(theme.collab_panel.channel_hash.width)
+    //                     .aligned()
+    //                     .left(),
+    //             )
+    //             .with_child(
+    //                 if let Some(pending_name) = self
+    //                     .channel_editing_state
+    //                     .as_ref()
+    //                     .and_then(|state| state.pending_name())
+    //                 {
+    //                     Label::new(
+    //                         pending_name.to_string(),
+    //                         theme.collab_panel.contact_username.text.clone(),
+    //                     )
+    //                     .contained()
+    //                     .with_style(theme.collab_panel.contact_username.container)
+    //                     .aligned()
+    //                     .left()
+    //                     .flex(1., true)
+    //                     .into_any()
+    //                 } else {
+    //                     ChildView::new(&self.channel_name_editor, cx)
+    //                         .aligned()
+    //                         .left()
+    //                         .contained()
+    //                         .with_style(theme.collab_panel.channel_editor)
+    //                         .flex(1.0, true)
+    //                         .into_any()
+    //                 },
+    //             )
+    //             .align_children_center()
+    //             .constrained()
+    //             .with_height(theme.collab_panel.row_height)
+    //             .contained()
+    //             .with_style(ContainerStyle {
+    //                 background_color: Some(theme.editor.background),
+    //                 ..*theme.collab_panel.contact_row.default_style()
+    //             })
+    //             .with_padding_left(
+    //                 theme.collab_panel.contact_row.default_style().padding.left
+    //                     + theme.collab_panel.channel_indent * depth as f32,
+    //             )
+    //             .into_any()
+    //     }
+
+    //     fn render_channel(
+    //         &self,
+    //         channel: &Channel,
+    //         depth: usize,
+    //         theme: &theme::Theme,
+    //         is_selected: bool,
+    //         has_children: bool,
+    //         ix: usize,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> AnyElement<Self> {
+    //         let channel_id = channel.id;
+    //         let collab_theme = &theme.collab_panel;
+    //         let is_public = self
+    //             .channel_store
+    //             .read(cx)
+    //             .channel_for_id(channel_id)
+    //             .map(|channel| channel.visibility)
+    //             == Some(proto::ChannelVisibility::Public);
+    //         let other_selected = self.selected_channel().map(|channel| channel.id) == Some(channel.id);
+    //         let disclosed =
+    //             has_children.then(|| !self.collapsed_channels.binary_search(&channel.id).is_ok());
+
+    //         let is_active = maybe!({
+    //             let call_channel = ActiveCall::global(cx)
+    //                 .read(cx)
+    //                 .room()?
+    //                 .read(cx)
+    //                 .channel_id()?;
+    //             Some(call_channel == channel_id)
+    //         })
+    //         .unwrap_or(false);
+
+    //         const FACEPILE_LIMIT: usize = 3;
+
+    //         enum ChannelCall {}
+    //         enum ChannelNote {}
+    //         enum NotesTooltip {}
+    //         enum ChatTooltip {}
+    //         enum ChannelTooltip {}
+
+    //         let mut is_dragged_over = false;
+    //         if cx
+    //             .global::<DragAndDrop<Workspace>>()
+    //             .currently_dragged::<Channel>(cx.window())
+    //             .is_some()
+    //             && self.drag_target_channel == ChannelDragTarget::Channel(channel_id)
+    //         {
+    //             is_dragged_over = true;
+    //         }
+
+    //         let has_messages_notification = channel.unseen_message_id.is_some();
+
+    //         MouseEventHandler::new::<Channel, _>(ix, cx, |state, cx| {
+    //             let row_hovered = state.hovered();
+
+    //             let mut select_state = |interactive: &Interactive<ContainerStyle>| {
+    //                 if state.clicked() == Some(MouseButton::Left) && interactive.clicked.is_some() {
+    //                     interactive.clicked.as_ref().unwrap().clone()
+    //                 } else if state.hovered() || other_selected {
+    //                     interactive
+    //                         .hovered
+    //                         .as_ref()
+    //                         .unwrap_or(&interactive.default)
+    //                         .clone()
+    //                 } else {
+    //                     interactive.default.clone()
+    //                 }
+    //             };
+
+    //             Flex::<Self>::row()
+    //                 .with_child(
+    //                     Svg::new(if is_public {
+    //                         "icons/public.svg"
+    //                     } else {
+    //                         "icons/hash.svg"
+    //                     })
+    //                     .with_color(collab_theme.channel_hash.color)
+    //                     .constrained()
+    //                     .with_width(collab_theme.channel_hash.width)
+    //                     .aligned()
+    //                     .left(),
+    //                 )
+    //                 .with_child({
+    //                     let style = collab_theme.channel_name.inactive_state();
+    //                     Flex::row()
+    //                         .with_child(
+    //                             Label::new(channel.name.clone(), style.text.clone())
+    //                                 .contained()
+    //                                 .with_style(style.container)
+    //                                 .aligned()
+    //                                 .left()
+    //                                 .with_tooltip::<ChannelTooltip>(
+    //                                     ix,
+    //                                     "Join channel",
+    //                                     None,
+    //                                     theme.tooltip.clone(),
+    //                                     cx,
+    //                                 ),
+    //                         )
+    //                         .with_children({
+    //                             let participants =
+    //                                 self.channel_store.read(cx).channel_participants(channel_id);
+
+    //                             if !participants.is_empty() {
+    //                                 let extra_count = participants.len().saturating_sub(FACEPILE_LIMIT);
+
+    //                                 let result = FacePile::new(collab_theme.face_overlap)
+    //                                     .with_children(
+    //                                         participants
+    //                                             .iter()
+    //                                             .filter_map(|user| {
+    //                                                 Some(
+    //                                                     Image::from_data(user.avatar.clone()?)
+    //                                                         .with_style(collab_theme.channel_avatar),
+    //                                                 )
+    //                                             })
+    //                                             .take(FACEPILE_LIMIT),
+    //                                     )
+    //                                     .with_children((extra_count > 0).then(|| {
+    //                                         Label::new(
+    //                                             format!("+{}", extra_count),
+    //                                             collab_theme.extra_participant_label.text.clone(),
+    //                                         )
+    //                                         .contained()
+    //                                         .with_style(collab_theme.extra_participant_label.container)
+    //                                     }));
+
+    //                                 Some(result)
+    //                             } else {
+    //                                 None
+    //                             }
+    //                         })
+    //                         .with_spacing(8.)
+    //                         .align_children_center()
+    //                         .flex(1., true)
+    //                 })
+    //                 .with_child(
+    //                     MouseEventHandler::new::<ChannelNote, _>(ix, cx, move |mouse_state, _| {
+    //                         let container_style = collab_theme
+    //                             .disclosure
+    //                             .button
+    //                             .style_for(mouse_state)
+    //                             .container;
+
+    //                         if channel.unseen_message_id.is_some() {
+    //                             Svg::new("icons/conversations.svg")
+    //                                 .with_color(collab_theme.channel_note_active_color)
+    //                                 .constrained()
+    //                                 .with_width(collab_theme.channel_hash.width)
+    //                                 .contained()
+    //                                 .with_style(container_style)
+    //                                 .with_uniform_padding(4.)
+    //                                 .into_any()
+    //                         } else if row_hovered {
+    //                             Svg::new("icons/conversations.svg")
+    //                                 .with_color(collab_theme.channel_hash.color)
+    //                                 .constrained()
+    //                                 .with_width(collab_theme.channel_hash.width)
+    //                                 .contained()
+    //                                 .with_style(container_style)
+    //                                 .with_uniform_padding(4.)
+    //                                 .into_any()
+    //                         } else {
+    //                             Empty::new().into_any()
+    //                         }
+    //                     })
+    //                     .on_click(MouseButton::Left, move |_, this, cx| {
+    //                         this.join_channel_chat(&JoinChannelChat { channel_id }, cx);
+    //                     })
+    //                     .with_tooltip::<ChatTooltip>(
+    //                         ix,
+    //                         "Open channel chat",
+    //                         None,
+    //                         theme.tooltip.clone(),
+    //                         cx,
+    //                     )
+    //                     .contained()
+    //                     .with_margin_right(4.),
+    //                 )
+    //                 .with_child(
+    //                     MouseEventHandler::new::<ChannelCall, _>(ix, cx, move |mouse_state, cx| {
+    //                         let container_style = collab_theme
+    //                             .disclosure
+    //                             .button
+    //                             .style_for(mouse_state)
+    //                             .container;
+    //                         if row_hovered || channel.unseen_note_version.is_some() {
+    //                             Svg::new("icons/file.svg")
+    //                                 .with_color(if channel.unseen_note_version.is_some() {
+    //                                     collab_theme.channel_note_active_color
+    //                                 } else {
+    //                                     collab_theme.channel_hash.color
+    //                                 })
+    //                                 .constrained()
+    //                                 .with_width(collab_theme.channel_hash.width)
+    //                                 .contained()
+    //                                 .with_style(container_style)
+    //                                 .with_uniform_padding(4.)
+    //                                 .with_margin_right(collab_theme.channel_hash.container.margin.left)
+    //                                 .with_tooltip::<NotesTooltip>(
+    //                                     ix as usize,
+    //                                     "Open channel notes",
+    //                                     None,
+    //                                     theme.tooltip.clone(),
+    //                                     cx,
+    //                                 )
+    //                                 .into_any()
+    //                         } else if has_messages_notification {
+    //                             Empty::new()
+    //                                 .constrained()
+    //                                 .with_width(collab_theme.channel_hash.width)
+    //                                 .contained()
+    //                                 .with_uniform_padding(4.)
+    //                                 .with_margin_right(collab_theme.channel_hash.container.margin.left)
+    //                                 .into_any()
+    //                         } else {
+    //                             Empty::new().into_any()
+    //                         }
+    //                     })
+    //                     .on_click(MouseButton::Left, move |_, this, cx| {
+    //                         this.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
+    //                     }),
+    //                 )
+    //                 .align_children_center()
+    //                 .styleable_component()
+    //                 .disclosable(
+    //                     disclosed,
+    //                     Box::new(ToggleCollapse {
+    //                         location: channel.id.clone(),
+    //                     }),
+    //                 )
+    //                 .with_id(ix)
+    //                 .with_style(collab_theme.disclosure.clone())
+    //                 .element()
+    //                 .constrained()
+    //                 .with_height(collab_theme.row_height)
+    //                 .contained()
+    //                 .with_style(select_state(
+    //                     collab_theme
+    //                         .channel_row
+    //                         .in_state(is_selected || is_active || is_dragged_over),
+    //                 ))
+    //                 .with_padding_left(
+    //                     collab_theme.channel_row.default_style().padding.left
+    //                         + collab_theme.channel_indent * depth as f32,
+    //                 )
+    //         })
+    //         .on_click(MouseButton::Left, move |_, this, cx| {
+    //             if this.drag_target_channel == ChannelDragTarget::None {
+    //                 if is_active {
+    //                     this.open_channel_notes(&OpenChannelNotes { channel_id }, cx)
+    //                 } else {
+    //                     this.join_channel(channel_id, cx)
+    //                 }
+    //             }
+    //         })
+    //         .on_click(MouseButton::Right, {
+    //             let channel = channel.clone();
+    //             move |e, this, cx| {
+    //                 this.deploy_channel_context_menu(Some(e.position), &channel, ix, cx);
+    //             }
+    //         })
+    //         .on_up(MouseButton::Left, move |_, this, cx| {
+    //             if let Some((_, dragged_channel)) = cx
+    //                 .global::<DragAndDrop<Workspace>>()
+    //                 .currently_dragged::<Channel>(cx.window())
+    //             {
+    //                 this.channel_store
+    //                     .update(cx, |channel_store, cx| {
+    //                         channel_store.move_channel(dragged_channel.id, Some(channel_id), cx)
+    //                     })
+    //                     .detach_and_log_err(cx)
+    //             }
+    //         })
+    //         .on_move({
+    //             let channel = channel.clone();
+    //             move |_, this, cx| {
+    //                 if let Some((_, dragged_channel)) = cx
+    //                     .global::<DragAndDrop<Workspace>>()
+    //                     .currently_dragged::<Channel>(cx.window())
+    //                 {
+    //                     if channel.id != dragged_channel.id {
+    //                         this.drag_target_channel = ChannelDragTarget::Channel(channel.id);
+    //                     }
+    //                     cx.notify()
+    //                 }
+    //             }
+    //         })
+    //         .as_draggable::<_, Channel>(
+    //             channel.clone(),
+    //             move |_, channel, cx: &mut ViewContext<Workspace>| {
+    //                 let theme = &theme::current(cx).collab_panel;
+
+    //                 Flex::<Workspace>::row()
+    //                     .with_child(
+    //                         Svg::new("icons/hash.svg")
+    //                             .with_color(theme.channel_hash.color)
+    //                             .constrained()
+    //                             .with_width(theme.channel_hash.width)
+    //                             .aligned()
+    //                             .left(),
+    //                     )
+    //                     .with_child(
+    //                         Label::new(channel.name.clone(), theme.channel_name.text.clone())
+    //                             .contained()
+    //                             .with_style(theme.channel_name.container)
+    //                             .aligned()
+    //                             .left(),
+    //                     )
+    //                     .align_children_center()
+    //                     .contained()
+    //                     .with_background_color(
+    //                         theme
+    //                             .container
+    //                             .background_color
+    //                             .unwrap_or(gpui::color::Color::transparent_black()),
+    //                     )
+    //                     .contained()
+    //                     .with_padding_left(
+    //                         theme.channel_row.default_style().padding.left
+    //                             + theme.channel_indent * depth as f32,
+    //                     )
+    //                     .into_any()
+    //             },
+    //         )
+    //         .with_cursor_style(CursorStyle::PointingHand)
+    //         .into_any()
+    //     }
+
+    //     fn render_channel_notes(
+    //         &self,
+    //         channel_id: ChannelId,
+    //         theme: &theme::CollabPanel,
+    //         is_selected: bool,
+    //         ix: usize,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> AnyElement<Self> {
+    //         enum ChannelNotes {}
+    //         let host_avatar_width = theme
+    //             .contact_avatar
+    //             .width
+    //             .or(theme.contact_avatar.height)
+    //             .unwrap_or(0.);
+
+    //         MouseEventHandler::new::<ChannelNotes, _>(ix as usize, cx, |state, cx| {
+    //             let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
+    //             let row = theme.project_row.in_state(is_selected).style_for(state);
+
+    //             Flex::<Self>::row()
+    //                 .with_child(render_tree_branch(
+    //                     tree_branch,
+    //                     &row.name.text,
+    //                     false,
+    //                     vec2f(host_avatar_width, theme.row_height),
+    //                     cx.font_cache(),
+    //                 ))
+    //                 .with_child(
+    //                     Svg::new("icons/file.svg")
+    //                         .with_color(theme.channel_hash.color)
+    //                         .constrained()
+    //                         .with_width(theme.channel_hash.width)
+    //                         .aligned()
+    //                         .left(),
+    //                 )
+    //                 .with_child(
+    //                     Label::new("notes", theme.channel_name.text.clone())
+    //                         .contained()
+    //                         .with_style(theme.channel_name.container)
+    //                         .aligned()
+    //                         .left()
+    //                         .flex(1., true),
+    //                 )
+    //                 .constrained()
+    //                 .with_height(theme.row_height)
+    //                 .contained()
+    //                 .with_style(*theme.channel_row.style_for(is_selected, state))
+    //                 .with_padding_left(theme.channel_row.default_style().padding.left)
+    //         })
+    //         .on_click(MouseButton::Left, move |_, this, cx| {
+    //             this.open_channel_notes(&OpenChannelNotes { channel_id }, cx);
+    //         })
+    //         .with_cursor_style(CursorStyle::PointingHand)
+    //         .into_any()
+    //     }
+
+    //     fn render_channel_chat(
+    //         &self,
+    //         channel_id: ChannelId,
+    //         theme: &theme::CollabPanel,
+    //         is_selected: bool,
+    //         ix: usize,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> AnyElement<Self> {
+    //         enum ChannelChat {}
+    //         let host_avatar_width = theme
+    //             .contact_avatar
+    //             .width
+    //             .or(theme.contact_avatar.height)
+    //             .unwrap_or(0.);
+
+    //         MouseEventHandler::new::<ChannelChat, _>(ix as usize, cx, |state, cx| {
+    //             let tree_branch = *theme.tree_branch.in_state(is_selected).style_for(state);
+    //             let row = theme.project_row.in_state(is_selected).style_for(state);
+
+    //             Flex::<Self>::row()
+    //                 .with_child(render_tree_branch(
+    //                     tree_branch,
+    //                     &row.name.text,
+    //                     true,
+    //                     vec2f(host_avatar_width, theme.row_height),
+    //                     cx.font_cache(),
+    //                 ))
+    //                 .with_child(
+    //                     Svg::new("icons/conversations.svg")
+    //                         .with_color(theme.channel_hash.color)
+    //                         .constrained()
+    //                         .with_width(theme.channel_hash.width)
+    //                         .aligned()
+    //                         .left(),
+    //                 )
+    //                 .with_child(
+    //                     Label::new("chat", theme.channel_name.text.clone())
+    //                         .contained()
+    //                         .with_style(theme.channel_name.container)
+    //                         .aligned()
+    //                         .left()
+    //                         .flex(1., true),
+    //                 )
+    //                 .constrained()
+    //                 .with_height(theme.row_height)
+    //                 .contained()
+    //                 .with_style(*theme.channel_row.style_for(is_selected, state))
+    //                 .with_padding_left(theme.channel_row.default_style().padding.left)
+    //         })
+    //         .on_click(MouseButton::Left, move |_, this, cx| {
+    //             this.join_channel_chat(&JoinChannelChat { channel_id }, cx);
+    //         })
+    //         .with_cursor_style(CursorStyle::PointingHand)
+    //         .into_any()
+    //     }
+
+    //     fn render_channel_invite(
+    //         channel: Arc<Channel>,
+    //         channel_store: ModelHandle<ChannelStore>,
+    //         theme: &theme::CollabPanel,
+    //         is_selected: bool,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> AnyElement<Self> {
+    //         enum Decline {}
+    //         enum Accept {}
+
+    //         let channel_id = channel.id;
+    //         let is_invite_pending = channel_store
+    //             .read(cx)
+    //             .has_pending_channel_invite_response(&channel);
+    //         let button_spacing = theme.contact_button_spacing;
+
+    //         Flex::row()
+    //             .with_child(
+    //                 Svg::new("icons/hash.svg")
+    //                     .with_color(theme.channel_hash.color)
+    //                     .constrained()
+    //                     .with_width(theme.channel_hash.width)
+    //                     .aligned()
+    //                     .left(),
+    //             )
+    //             .with_child(
+    //                 Label::new(channel.name.clone(), theme.contact_username.text.clone())
+    //                     .contained()
+    //                     .with_style(theme.contact_username.container)
+    //                     .aligned()
+    //                     .left()
+    //                     .flex(1., true),
+    //             )
+    //             .with_child(
+    //                 MouseEventHandler::new::<Decline, _>(channel.id as usize, cx, |mouse_state, _| {
+    //                     let button_style = if is_invite_pending {
+    //                         &theme.disabled_button
+    //                     } else {
+    //                         theme.contact_button.style_for(mouse_state)
+    //                     };
+    //                     render_icon_button(button_style, "icons/x.svg").aligned()
+    //                 })
+    //                 .with_cursor_style(CursorStyle::PointingHand)
+    //                 .on_click(MouseButton::Left, move |_, this, cx| {
+    //                     this.respond_to_channel_invite(channel_id, false, cx);
+    //                 })
+    //                 .contained()
+    //                 .with_margin_right(button_spacing),
+    //             )
+    //             .with_child(
+    //                 MouseEventHandler::new::<Accept, _>(channel.id as usize, cx, |mouse_state, _| {
+    //                     let button_style = if is_invite_pending {
+    //                         &theme.disabled_button
+    //                     } else {
+    //                         theme.contact_button.style_for(mouse_state)
+    //                     };
+    //                     render_icon_button(button_style, "icons/check.svg")
+    //                         .aligned()
+    //                         .flex_float()
+    //                 })
+    //                 .with_cursor_style(CursorStyle::PointingHand)
+    //                 .on_click(MouseButton::Left, move |_, this, cx| {
+    //                     this.respond_to_channel_invite(channel_id, true, cx);
+    //                 }),
+    //             )
+    //             .constrained()
+    //             .with_height(theme.row_height)
+    //             .contained()
+    //             .with_style(
+    //                 *theme
+    //                     .contact_row
+    //                     .in_state(is_selected)
+    //                     .style_for(&mut Default::default()),
+    //             )
+    //             .with_padding_left(
+    //                 theme.contact_row.default_style().padding.left + theme.channel_indent,
+    //             )
+    //             .into_any()
+    //     }
+
+    //     fn render_contact_request(
+    //         user: Arc<User>,
+    //         user_store: ModelHandle<UserStore>,
+    //         theme: &theme::CollabPanel,
+    //         is_incoming: bool,
+    //         is_selected: bool,
+    //         cx: &mut ViewContext<Self>,
+    //     ) -> AnyElement<Self> {
+    //         enum Decline {}
+    //         enum Accept {}
+    //         enum Cancel {}
+
+    //         let mut row = Flex::row()
+    //             .with_children(user.avatar.clone().map(|avatar| {
+    //                 Image::from_data(avatar)
+    //                     .with_style(theme.contact_avatar)
+    //                     .aligned()
+    //                     .left()
+    //             }))
+    //             .with_child(
+    //                 Label::new(
+    //                     user.github_login.clone(),
+    //                     theme.contact_username.text.clone(),
+    //                 )
+    //                 .contained()
+    //                 .with_style(theme.contact_username.container)
+    //                 .aligned()
+    //                 .left()
+    //                 .flex(1., true),
+    //             );
+
+    //         let user_id = user.id;
+    //         let github_login = user.github_login.clone();
+    //         let is_contact_request_pending = user_store.read(cx).is_contact_request_pending(&user);
+    //         let button_spacing = theme.contact_button_spacing;
+
+    //         if is_incoming {
+    //             row.add_child(
+    //                 MouseEventHandler::new::<Decline, _>(user.id as usize, cx, |mouse_state, _| {
+    //                     let button_style = if is_contact_request_pending {
+    //                         &theme.disabled_button
+    //                     } else {
+    //                         theme.contact_button.style_for(mouse_state)
+    //                     };
+    //                     render_icon_button(button_style, "icons/x.svg").aligned()
+    //                 })
+    //                 .with_cursor_style(CursorStyle::PointingHand)
+    //                 .on_click(MouseButton::Left, move |_, this, cx| {
+    //                     this.respond_to_contact_request(user_id, false, cx);
+    //                 })
+    //                 .contained()
+    //                 .with_margin_right(button_spacing),
+    //             );
+
+    //             row.add_child(
+    //                 MouseEventHandler::new::<Accept, _>(user.id as usize, cx, |mouse_state, _| {
+    //                     let button_style = if is_contact_request_pending {
+    //                         &theme.disabled_button
+    //                     } else {
+    //                         theme.contact_button.style_for(mouse_state)
+    //                     };
+    //                     render_icon_button(button_style, "icons/check.svg")
+    //                         .aligned()
+    //                         .flex_float()
+    //                 })
+    //                 .with_cursor_style(CursorStyle::PointingHand)
+    //                 .on_click(MouseButton::Left, move |_, this, cx| {
+    //                     this.respond_to_contact_request(user_id, true, cx);
+    //                 }),
+    //             );
+    //         } else {
+    //             row.add_child(
+    //                 MouseEventHandler::new::<Cancel, _>(user.id as usize, cx, |mouse_state, _| {
+    //                     let button_style = if is_contact_request_pending {
+    //                         &theme.disabled_button
+    //                     } else {
+    //                         theme.contact_button.style_for(mouse_state)
+    //                     };
+    //                     render_icon_button(button_style, "icons/x.svg")
+    //                         .aligned()
+    //                         .flex_float()
+    //                 })
+    //                 .with_padding(Padding::uniform(2.))
+    //                 .with_cursor_style(CursorStyle::PointingHand)
+    //                 .on_click(MouseButton::Left, move |_, this, cx| {
+    //                     this.remove_contact(user_id, &github_login, cx);
+    //                 })
+    //                 .flex_float(),
+    //             );
+    //         }
+
+    //         row.constrained()
+    //             .with_height(theme.row_height)
+    //             .contained()
+    //             .with_style(
+    //                 *theme
+    //                     .contact_row
+    //                     .in_state(is_selected)
+    //                     .style_for(&mut Default::default()),
+    //             )
+    //             .into_any()
+    //     }
+
+    //     fn has_subchannels(&self, ix: usize) -> bool {
+    //         self.entries.get(ix).map_or(false, |entry| {
+    //             if let ListEntry::Channel { has_children, .. } = entry {
+    //                 *has_children
+    //             } else {
+    //                 false
+    //             }
+    //         })
+    //     }
+
+    //     fn deploy_channel_context_menu(
+    //         &mut self,
+    //         position: Option<Vector2F>,
+    //         channel: &Channel,
+    //         ix: usize,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         self.context_menu_on_selected = position.is_none();
+
+    //         let clipboard_channel_name = self.channel_clipboard.as_ref().and_then(|clipboard| {
+    //             self.channel_store
+    //                 .read(cx)
+    //                 .channel_for_id(clipboard.channel_id)
+    //                 .map(|channel| channel.name.clone())
+    //         });
+
+    //         self.context_menu.update(cx, |context_menu, cx| {
+    //             context_menu.set_position_mode(if self.context_menu_on_selected {
+    //                 OverlayPositionMode::Local
+    //             } else {
+    //                 OverlayPositionMode::Window
+    //             });
+
+    //             let mut items = Vec::new();
+
+    //             let select_action_name = if self.selection == Some(ix) {
+    //                 "Unselect"
+    //             } else {
+    //                 "Select"
+    //             };
+
+    //             items.push(ContextMenuItem::action(
+    //                 select_action_name,
+    //                 ToggleSelectedIx { ix },
+    //             ));
+
+    //             if self.has_subchannels(ix) {
+    //                 let expand_action_name = if self.is_channel_collapsed(channel.id) {
+    //                     "Expand Subchannels"
+    //                 } else {
+    //                     "Collapse Subchannels"
+    //                 };
+    //                 items.push(ContextMenuItem::action(
+    //                     expand_action_name,
+    //                     ToggleCollapse {
+    //                         location: channel.id,
+    //                     },
+    //                 ));
+    //             }
+
+    //             items.push(ContextMenuItem::action(
+    //                 "Open Notes",
+    //                 OpenChannelNotes {
+    //                     channel_id: channel.id,
+    //                 },
+    //             ));
+
+    //             items.push(ContextMenuItem::action(
+    //                 "Open Chat",
+    //                 JoinChannelChat {
+    //                     channel_id: channel.id,
+    //                 },
+    //             ));
+
+    //             items.push(ContextMenuItem::action(
+    //                 "Copy Channel Link",
+    //                 CopyChannelLink {
+    //                     channel_id: channel.id,
+    //                 },
+    //             ));
+
+    //             if self.channel_store.read(cx).is_channel_admin(channel.id) {
+    //                 items.extend([
+    //                     ContextMenuItem::Separator,
+    //                     ContextMenuItem::action(
+    //                         "New Subchannel",
+    //                         NewChannel {
+    //                             location: channel.id,
+    //                         },
+    //                     ),
+    //                     ContextMenuItem::action(
+    //                         "Rename",
+    //                         RenameChannel {
+    //                             channel_id: channel.id,
+    //                         },
+    //                     ),
+    //                     ContextMenuItem::action(
+    //                         "Move this channel",
+    //                         StartMoveChannelFor {
+    //                             channel_id: channel.id,
+    //                         },
+    //                     ),
+    //                 ]);
+
+    //                 if let Some(channel_name) = clipboard_channel_name {
+    //                     items.push(ContextMenuItem::Separator);
+    //                     items.push(ContextMenuItem::action(
+    //                         format!("Move '#{}' here", channel_name),
+    //                         MoveChannel { to: channel.id },
+    //                     ));
+    //                 }
+
+    //                 items.extend([
+    //                     ContextMenuItem::Separator,
+    //                     ContextMenuItem::action(
+    //                         "Invite Members",
+    //                         InviteMembers {
+    //                             channel_id: channel.id,
+    //                         },
+    //                     ),
+    //                     ContextMenuItem::action(
+    //                         "Manage Members",
+    //                         ManageMembers {
+    //                             channel_id: channel.id,
+    //                         },
+    //                     ),
+    //                     ContextMenuItem::Separator,
+    //                     ContextMenuItem::action(
+    //                         "Delete",
+    //                         RemoveChannel {
+    //                             channel_id: channel.id,
+    //                         },
+    //                     ),
+    //                 ]);
+    //             }
+
+    //             context_menu.show(
+    //                 position.unwrap_or_default(),
+    //                 if self.context_menu_on_selected {
+    //                     gpui::elements::AnchorCorner::TopRight
+    //                 } else {
+    //                     gpui::elements::AnchorCorner::BottomLeft
+    //                 },
+    //                 items,
+    //                 cx,
+    //             );
+    //         });
+
+    //         cx.notify();
+    //     }
+
+    //     fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext<Self>) {
+    //         if self.take_editing_state(cx) {
+    //             cx.focus(&self.filter_editor);
+    //         } else {
+    //             self.filter_editor.update(cx, |editor, cx| {
+    //                 if editor.buffer().read(cx).len(cx) > 0 {
+    //                     editor.set_text("", cx);
+    //                 }
+    //             });
+    //         }
+
+    //         self.update_entries(false, cx);
+    //     }
+
+    //     fn select_next(&mut self, _: &SelectNext, cx: &mut ViewContext<Self>) {
+    //         let ix = self.selection.map_or(0, |ix| ix + 1);
+    //         if ix < self.entries.len() {
+    //             self.selection = Some(ix);
+    //         }
+
+    //         self.list_state.reset(self.entries.len());
+    //         if let Some(ix) = self.selection {
+    //             self.list_state.scroll_to(ListOffset {
+    //                 item_ix: ix,
+    //                 offset_in_item: 0.,
+    //             });
+    //         }
+    //         cx.notify();
+    //     }
+
+    //     fn select_prev(&mut self, _: &SelectPrev, cx: &mut ViewContext<Self>) {
+    //         let ix = self.selection.take().unwrap_or(0);
+    //         if ix > 0 {
+    //             self.selection = Some(ix - 1);
+    //         }
+
+    //         self.list_state.reset(self.entries.len());
+    //         if let Some(ix) = self.selection {
+    //             self.list_state.scroll_to(ListOffset {
+    //                 item_ix: ix,
+    //                 offset_in_item: 0.,
+    //             });
+    //         }
+    //         cx.notify();
+    //     }
+
+    //     fn confirm(&mut self, _: &Confirm, cx: &mut ViewContext<Self>) {
+    //         if self.confirm_channel_edit(cx) {
+    //             return;
+    //         }
+
+    //         if let Some(selection) = self.selection {
+    //             if let Some(entry) = self.entries.get(selection) {
+    //                 match entry {
+    //                     ListEntry::Header(section) => match section {
+    //                         Section::ActiveCall => Self::leave_call(cx),
+    //                         Section::Channels => self.new_root_channel(cx),
+    //                         Section::Contacts => self.toggle_contact_finder(cx),
+    //                         Section::ContactRequests
+    //                         | Section::Online
+    //                         | Section::Offline
+    //                         | Section::ChannelInvites => {
+    //                             self.toggle_section_expanded(*section, cx);
+    //                         }
+    //                     },
+    //                     ListEntry::Contact { contact, calling } => {
+    //                         if contact.online && !contact.busy && !calling {
+    //                             self.call(contact.user.id, Some(self.project.clone()), cx);
+    //                         }
+    //                     }
+    //                     ListEntry::ParticipantProject {
+    //                         project_id,
+    //                         host_user_id,
+    //                         ..
+    //                     } => {
+    //                         if let Some(workspace) = self.workspace.upgrade(cx) {
+    //                             let app_state = workspace.read(cx).app_state().clone();
+    //                             workspace::join_remote_project(
+    //                                 *project_id,
+    //                                 *host_user_id,
+    //                                 app_state,
+    //                                 cx,
+    //                             )
+    //                             .detach_and_log_err(cx);
+    //                         }
+    //                     }
+    //                     ListEntry::ParticipantScreen { peer_id, .. } => {
+    //                         let Some(peer_id) = peer_id else {
+    //                             return;
+    //                         };
+    //                         if let Some(workspace) = self.workspace.upgrade(cx) {
+    //                             workspace.update(cx, |workspace, cx| {
+    //                                 workspace.open_shared_screen(*peer_id, cx)
+    //                             });
+    //                         }
+    //                     }
+    //                     ListEntry::Channel { channel, .. } => {
+    //                         let is_active = maybe!({
+    //                             let call_channel = ActiveCall::global(cx)
+    //                                 .read(cx)
+    //                                 .room()?
+    //                                 .read(cx)
+    //                                 .channel_id()?;
+
+    //                             Some(call_channel == channel.id)
+    //                         })
+    //                         .unwrap_or(false);
+    //                         if is_active {
+    //                             self.open_channel_notes(
+    //                                 &OpenChannelNotes {
+    //                                     channel_id: channel.id,
+    //                                 },
+    //                                 cx,
+    //                             )
+    //                         } else {
+    //                             self.join_channel(channel.id, cx)
+    //                         }
+    //                     }
+    //                     ListEntry::ContactPlaceholder => self.toggle_contact_finder(cx),
+    //                     _ => {}
+    //                 }
+    //             }
+    //         }
+    //     }
+
+    //     fn insert_space(&mut self, _: &InsertSpace, cx: &mut ViewContext<Self>) {
+    //         if self.channel_editing_state.is_some() {
+    //             self.channel_name_editor.update(cx, |editor, cx| {
+    //                 editor.insert(" ", cx);
+    //             });
+    //         }
+    //     }
+
+    //     fn confirm_channel_edit(&mut self, cx: &mut ViewContext<CollabPanel>) -> bool {
+    //         if let Some(editing_state) = &mut self.channel_editing_state {
+    //             match editing_state {
+    //                 ChannelEditingState::Create {
+    //                     location,
+    //                     pending_name,
+    //                     ..
+    //                 } => {
+    //                     if pending_name.is_some() {
+    //                         return false;
+    //                     }
+    //                     let channel_name = self.channel_name_editor.read(cx).text(cx);
+
+    //                     *pending_name = Some(channel_name.clone());
+
+    //                     self.channel_store
+    //                         .update(cx, |channel_store, cx| {
+    //                             channel_store.create_channel(&channel_name, *location, cx)
+    //                         })
+    //                         .detach();
+    //                     cx.notify();
+    //                 }
+    //                 ChannelEditingState::Rename {
+    //                     location,
+    //                     pending_name,
+    //                 } => {
+    //                     if pending_name.is_some() {
+    //                         return false;
+    //                     }
+    //                     let channel_name = self.channel_name_editor.read(cx).text(cx);
+    //                     *pending_name = Some(channel_name.clone());
+
+    //                     self.channel_store
+    //                         .update(cx, |channel_store, cx| {
+    //                             channel_store.rename(*location, &channel_name, cx)
+    //                         })
+    //                         .detach();
+    //                     cx.notify();
+    //                 }
+    //             }
+    //             cx.focus_self();
+    //             true
+    //         } else {
+    //             false
+    //         }
+    //     }
+
+    //     fn toggle_section_expanded(&mut self, section: Section, cx: &mut ViewContext<Self>) {
+    //         if let Some(ix) = self.collapsed_sections.iter().position(|s| *s == section) {
+    //             self.collapsed_sections.remove(ix);
+    //         } else {
+    //             self.collapsed_sections.push(section);
+    //         }
+    //         self.update_entries(false, cx);
+    //     }
+
+    //     fn collapse_selected_channel(
+    //         &mut self,
+    //         _: &CollapseSelectedChannel,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         let Some(channel_id) = self.selected_channel().map(|channel| channel.id) else {
+    //             return;
+    //         };
+
+    //         if self.is_channel_collapsed(channel_id) {
+    //             return;
+    //         }
+
+    //         self.toggle_channel_collapsed(channel_id, cx);
+    //     }
+
+    //     fn expand_selected_channel(&mut self, _: &ExpandSelectedChannel, cx: &mut ViewContext<Self>) {
+    //         let Some(id) = self.selected_channel().map(|channel| channel.id) else {
+    //             return;
+    //         };
+
+    //         if !self.is_channel_collapsed(id) {
+    //             return;
+    //         }
+
+    //         self.toggle_channel_collapsed(id, cx)
+    //     }
+
+    //     fn toggle_channel_collapsed_action(
+    //         &mut self,
+    //         action: &ToggleCollapse,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         self.toggle_channel_collapsed(action.location, cx);
+    //     }
+
+    //     fn toggle_channel_collapsed<'a>(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
+    //         match self.collapsed_channels.binary_search(&channel_id) {
+    //             Ok(ix) => {
+    //                 self.collapsed_channels.remove(ix);
+    //             }
+    //             Err(ix) => {
+    //                 self.collapsed_channels.insert(ix, channel_id);
+    //             }
+    //         };
+    //         self.serialize(cx);
+    //         self.update_entries(true, cx);
+    //         cx.notify();
+    //         cx.focus_self();
+    //     }
+
+    //     fn is_channel_collapsed(&self, channel_id: ChannelId) -> bool {
+    //         self.collapsed_channels.binary_search(&channel_id).is_ok()
+    //     }
+
+    //     fn leave_call(cx: &mut ViewContext<Self>) {
+    //         ActiveCall::global(cx)
+    //             .update(cx, |call, cx| call.hang_up(cx))
+    //             .detach_and_log_err(cx);
+    //     }
+
+    //     fn toggle_contact_finder(&mut self, cx: &mut ViewContext<Self>) {
+    //         if let Some(workspace) = self.workspace.upgrade(cx) {
+    //             workspace.update(cx, |workspace, cx| {
+    //                 workspace.toggle_modal(cx, |_, cx| {
+    //                     cx.add_view(|cx| {
+    //                         let mut finder = ContactFinder::new(self.user_store.clone(), cx);
+    //                         finder.set_query(self.filter_editor.read(cx).text(cx), cx);
+    //                         finder
+    //                     })
+    //                 });
+    //             });
+    //         }
+    //     }
+
+    //     fn new_root_channel(&mut self, cx: &mut ViewContext<Self>) {
+    //         self.channel_editing_state = Some(ChannelEditingState::Create {
+    //             location: None,
+    //             pending_name: None,
+    //         });
+    //         self.update_entries(false, cx);
+    //         self.select_channel_editor();
+    //         cx.focus(self.channel_name_editor.as_any());
+    //         cx.notify();
+    //     }
+
+    //     fn select_channel_editor(&mut self) {
+    //         self.selection = self.entries.iter().position(|entry| match entry {
+    //             ListEntry::ChannelEditor { .. } => true,
+    //             _ => false,
+    //         });
+    //     }
+
+    //     fn new_subchannel(&mut self, action: &NewChannel, cx: &mut ViewContext<Self>) {
+    //         self.collapsed_channels
+    //             .retain(|channel| *channel != action.location);
+    //         self.channel_editing_state = Some(ChannelEditingState::Create {
+    //             location: Some(action.location.to_owned()),
+    //             pending_name: None,
+    //         });
+    //         self.update_entries(false, cx);
+    //         self.select_channel_editor();
+    //         cx.focus(self.channel_name_editor.as_any());
+    //         cx.notify();
+    //     }
+
+    //     fn invite_members(&mut self, action: &InviteMembers, cx: &mut ViewContext<Self>) {
+    //         self.show_channel_modal(action.channel_id, channel_modal::Mode::InviteMembers, cx);
+    //     }
+
+    //     fn manage_members(&mut self, action: &ManageMembers, cx: &mut ViewContext<Self>) {
+    //         self.show_channel_modal(action.channel_id, channel_modal::Mode::ManageMembers, cx);
+    //     }
+
+    //     fn remove(&mut self, _: &Remove, cx: &mut ViewContext<Self>) {
+    //         if let Some(channel) = self.selected_channel() {
+    //             self.remove_channel(channel.id, cx)
+    //         }
+    //     }
+
+    //     fn rename_selected_channel(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext<Self>) {
+    //         if let Some(channel) = self.selected_channel() {
+    //             self.rename_channel(
+    //                 &RenameChannel {
+    //                     channel_id: channel.id,
+    //                 },
+    //                 cx,
+    //             );
+    //         }
+    //     }
+
+    //     fn rename_channel(&mut self, action: &RenameChannel, cx: &mut ViewContext<Self>) {
+    //         let channel_store = self.channel_store.read(cx);
+    //         if !channel_store.is_channel_admin(action.channel_id) {
+    //             return;
+    //         }
+    //         if let Some(channel) = channel_store.channel_for_id(action.channel_id).cloned() {
+    //             self.channel_editing_state = Some(ChannelEditingState::Rename {
+    //                 location: action.channel_id.to_owned(),
+    //                 pending_name: None,
+    //             });
+    //             self.channel_name_editor.update(cx, |editor, cx| {
+    //                 editor.set_text(channel.name.clone(), cx);
+    //                 editor.select_all(&Default::default(), cx);
+    //             });
+    //             cx.focus(self.channel_name_editor.as_any());
+    //             self.update_entries(false, cx);
+    //             self.select_channel_editor();
+    //         }
+    //     }
+
+    //     fn open_channel_notes(&mut self, action: &OpenChannelNotes, cx: &mut ViewContext<Self>) {
+    //         if let Some(workspace) = self.workspace.upgrade(cx) {
+    //             ChannelView::open(action.channel_id, workspace, cx).detach();
+    //         }
+    //     }
+
+    //     fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext<Self>) {
+    //         let Some(channel) = self.selected_channel() else {
+    //             return;
+    //         };
+
+    //         self.deploy_channel_context_menu(None, &channel.clone(), self.selection.unwrap(), cx);
+    //     }
+
+    //     fn selected_channel(&self) -> Option<&Arc<Channel>> {
+    //         self.selection
+    //             .and_then(|ix| self.entries.get(ix))
+    //             .and_then(|entry| match entry {
+    //                 ListEntry::Channel { channel, .. } => Some(channel),
+    //                 _ => None,
+    //             })
+    //     }
+
+    //     fn show_channel_modal(
+    //         &mut self,
+    //         channel_id: ChannelId,
+    //         mode: channel_modal::Mode,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         let workspace = self.workspace.clone();
+    //         let user_store = self.user_store.clone();
+    //         let channel_store = self.channel_store.clone();
+    //         let members = self.channel_store.update(cx, |channel_store, cx| {
+    //             channel_store.get_channel_member_details(channel_id, cx)
+    //         });
+
+    //         cx.spawn(|_, mut cx| async move {
+    //             let members = members.await?;
+    //             workspace.update(&mut cx, |workspace, cx| {
+    //                 workspace.toggle_modal(cx, |_, cx| {
+    //                     cx.add_view(|cx| {
+    //                         ChannelModal::new(
+    //                             user_store.clone(),
+    //                             channel_store.clone(),
+    //                             channel_id,
+    //                             mode,
+    //                             members,
+    //                             cx,
+    //                         )
+    //                     })
+    //                 });
+    //             })
+    //         })
+    //         .detach();
+    //     }
+
+    //     fn remove_selected_channel(&mut self, action: &RemoveChannel, cx: &mut ViewContext<Self>) {
+    //         self.remove_channel(action.channel_id, cx)
+    //     }
+
+    //     fn remove_channel(&mut self, channel_id: ChannelId, cx: &mut ViewContext<Self>) {
+    //         let channel_store = self.channel_store.clone();
+    //         if let Some(channel) = channel_store.read(cx).channel_for_id(channel_id) {
+    //             let prompt_message = format!(
+    //                 "Are you sure you want to remove the channel \"{}\"?",
+    //                 channel.name
+    //             );
+    //             let mut answer =
+    //                 cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
+    //             let window = cx.window();
+    //             cx.spawn(|this, mut cx| async move {
+    //                 if answer.next().await == Some(0) {
+    //                     if let Err(e) = channel_store
+    //                         .update(&mut cx, |channels, _| channels.remove_channel(channel_id))
+    //                         .await
+    //                     {
+    //                         window.prompt(
+    //                             PromptLevel::Info,
+    //                             &format!("Failed to remove channel: {}", e),
+    //                             &["Ok"],
+    //                             &mut cx,
+    //                         );
+    //                     }
+    //                     this.update(&mut cx, |_, cx| cx.focus_self()).ok();
+    //                 }
+    //             })
+    //             .detach();
+    //         }
+    //     }
+
+    //     // Should move to the filter editor if clicking on it
+    //     // Should move selection to the channel editor if activating it
+
+    //     fn remove_contact(&mut self, user_id: u64, github_login: &str, cx: &mut ViewContext<Self>) {
+    //         let user_store = self.user_store.clone();
+    //         let prompt_message = format!(
+    //             "Are you sure you want to remove \"{}\" from your contacts?",
+    //             github_login
+    //         );
+    //         let mut answer = cx.prompt(PromptLevel::Warning, &prompt_message, &["Remove", "Cancel"]);
+    //         let window = cx.window();
+    //         cx.spawn(|_, mut cx| async move {
+    //             if answer.next().await == Some(0) {
+    //                 if let Err(e) = user_store
+    //                     .update(&mut cx, |store, cx| store.remove_contact(user_id, cx))
+    //                     .await
+    //                 {
+    //                     window.prompt(
+    //                         PromptLevel::Info,
+    //                         &format!("Failed to remove contact: {}", e),
+    //                         &["Ok"],
+    //                         &mut cx,
+    //                     );
+    //                 }
+    //             }
+    //         })
+    //         .detach();
+    //     }
+
+    //     fn respond_to_contact_request(
+    //         &mut self,
+    //         user_id: u64,
+    //         accept: bool,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         self.user_store
+    //             .update(cx, |store, cx| {
+    //                 store.respond_to_contact_request(user_id, accept, cx)
+    //             })
+    //             .detach();
+    //     }
+
+    //     fn respond_to_channel_invite(
+    //         &mut self,
+    //         channel_id: u64,
+    //         accept: bool,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         self.channel_store
+    //             .update(cx, |store, cx| {
+    //                 store.respond_to_channel_invite(channel_id, accept, cx)
+    //             })
+    //             .detach();
+    //     }
+
+    //     fn call(
+    //         &mut self,
+    //         recipient_user_id: u64,
+    //         initial_project: Option<ModelHandle<Project>>,
+    //         cx: &mut ViewContext<Self>,
+    //     ) {
+    //         ActiveCall::global(cx)
+    //             .update(cx, |call, cx| {
+    //                 call.invite(recipient_user_id, initial_project, cx)
+    //             })
+    //             .detach_and_log_err(cx);
+    //     }
+
+    //     fn join_channel(&self, channel_id: u64, cx: &mut ViewContext<Self>) {
+    //         let Some(workspace) = self.workspace.upgrade(cx) else {
+    //             return;
+    //         };
+    //         let Some(handle) = cx.window().downcast::<Workspace>() else {
+    //             return;
+    //         };
+    //         workspace::join_channel(
+    //             channel_id,
+    //             workspace.read(cx).app_state().clone(),
+    //             Some(handle),
+    //             cx,
+    //         )
+    //         .detach_and_log_err(cx)
+    //     }
+
+    //     fn join_channel_chat(&mut self, action: &JoinChannelChat, cx: &mut ViewContext<Self>) {
+    //         let channel_id = action.channel_id;
+    //         if let Some(workspace) = self.workspace.upgrade(cx) {
+    //             cx.app_context().defer(move |cx| {
+    //                 workspace.update(cx, |workspace, cx| {
+    //                     if let Some(panel) = workspace.focus_panel::<ChatPanel>(cx) {
+    //                         panel.update(cx, |panel, cx| {
+    //                             panel
+    //                                 .select_channel(channel_id, None, cx)
+    //                                 .detach_and_log_err(cx);
+    //                         });
+    //                     }
+    //                 });
+    //             });
+    //         }
+    //     }
+
+    //     fn copy_channel_link(&mut self, action: &CopyChannelLink, cx: &mut ViewContext<Self>) {
+    //         let channel_store = self.channel_store.read(cx);
+    //         let Some(channel) = channel_store.channel_for_id(action.channel_id) else {
+    //             return;
+    //         };
+    //         let item = ClipboardItem::new(channel.link());
+    //         cx.write_to_clipboard(item)
+    //     }
+}
+
 // fn render_tree_branch(
 //     branch_style: theme::TreeBranch,
 //     row_style: &TextStyle,

crates/collab_ui2/src/collab_ui.rs 🔗

@@ -9,6 +9,7 @@ mod panel_settings;
 
 use std::sync::Arc;
 
+pub use collab_panel::CollabPanel;
 pub use collab_titlebar_item::CollabTitlebarItem;
 use gpui::AppContext;
 pub use panel_settings::{
@@ -29,7 +30,7 @@ pub fn init(_app_state: &Arc<AppState>, cx: &mut AppContext) {
 
     // vcs_menu::init(cx);
     collab_titlebar_item::init(cx);
-    // collab_panel::init(cx);
+    collab_panel::init(cx);
     // chat_panel::init(cx);
     // notifications::init(&app_state, cx);
 

crates/zed2/src/zed2.rs 🔗

@@ -383,8 +383,8 @@ pub fn initialize_workspace(
         let project_panel = ProjectPanel::load(workspace_handle.clone(), cx.clone());
         // let terminal_panel = TerminalPanel::load(workspace_handle.clone(), cx.clone());
         // let assistant_panel = AssistantPanel::load(workspace_handle.clone(), cx.clone());
-        // let channels_panel =
-        //     collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone());
+        let channels_panel =
+            collab_ui::collab_panel::CollabPanel::load(workspace_handle.clone(), cx.clone());
         // let chat_panel =
         //     collab_ui::chat_panel::ChatPanel::load(workspace_handle.clone(), cx.clone());
         // let notification_panel = collab_ui::notification_panel::NotificationPanel::load(
@@ -395,16 +395,16 @@ pub fn initialize_workspace(
             project_panel,
             //     terminal_panel,
             //     assistant_panel,
-            //     channels_panel,
+            channels_panel,
             //     chat_panel,
             //     notification_panel,
         ) = futures::try_join!(
             project_panel,
             //     terminal_panel,
             //     assistant_panel,
-            //     channels_panel,
+            channels_panel,
             //     chat_panel,
-            //     notification_panel,
+            //     notification_panel,/
         )?;
 
         workspace_handle.update(&mut cx, |workspace, cx| {
@@ -412,7 +412,7 @@ pub fn initialize_workspace(
             workspace.add_panel(project_panel, cx);
             //     workspace.add_panel(terminal_panel, cx);
             //     workspace.add_panel(assistant_panel, cx);
-            //     workspace.add_panel(channels_panel, cx);
+            workspace.add_panel(channels_panel, cx);
             //     workspace.add_panel(chat_panel, cx);
             //     workspace.add_panel(notification_panel, cx);