diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index 4415a10625..2aeff3afef 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -11,6 +11,7 @@ use std::{mem, sync::Arc, time::Duration}; use util::ResultExt; use self::channel_index::ChannelIndex; +pub use self::channel_index::ChannelPath; pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); @@ -145,11 +146,13 @@ impl ChannelStore { }) } - pub fn channel_at_index(&self, ix: usize) -> Option<(usize, &Arc)> { + pub fn channel_at_index(&self, ix: usize) -> Option<(usize, &Arc, &Arc<[ChannelId]>)> { let path = self.channel_index.get(ix)?; let id = path.last().unwrap(); let channel = self.channel_for_id(*id).unwrap(); - Some((path.len() - 1, channel)) + + + Some((path.len() - 1, channel, path)) } pub fn channel_invitations(&self) -> &[Arc] { @@ -734,12 +737,15 @@ impl ChannelStore { } } - self.channel_index.insert_channels(payload.channels); + let mut channel_index = self.channel_index.start_upsert(); + for channel in payload.channels { + channel_index.upsert(channel) + } } for edge in payload.delete_channel_edge { self.channel_index - .remove_edge(edge.parent_id, edge.channel_id); + .delete_edge(edge.parent_id, edge.channel_id); } for permission in payload.channel_permissions { diff --git a/crates/channel/src/channel_store/channel_index.rs b/crates/channel/src/channel_store/channel_index.rs index b9398d099c..b52d7ba334 100644 --- a/crates/channel/src/channel_store/channel_index.rs +++ b/crates/channel/src/channel_store/channel_index.rs @@ -1,11 +1,11 @@ -use std::{ops::{Deref, DerefMut}, sync::Arc}; +use std::{ops::Deref, sync::Arc}; use collections::HashMap; use rpc::proto; use crate::{ChannelId, Channel}; -pub type ChannelPath = Vec; +pub type ChannelPath = Arc<[ChannelId]>; pub type ChannelsById = HashMap>; #[derive(Default, Debug)] @@ -20,33 +20,6 @@ impl ChannelIndex { &self.channels_by_id } - /// Insert or update all of the given channels into the index - pub fn insert_channels(&mut self, channels: Vec) { - let mut insert = self.insert(); - - for channel_proto in channels { - if let Some(existing_channel) = insert.channels_by_id.get_mut(&channel_proto.id) { - Arc::make_mut(existing_channel).name = channel_proto.name; - - if let Some(parent_id) = channel_proto.parent_id { - insert.insert_edge(parent_id, channel_proto.id) - } - } else { - let channel = Arc::new(Channel { - id: channel_proto.id, - name: channel_proto.name, - }); - insert.channels_by_id.insert(channel.id, channel.clone()); - - if let Some(parent_id) = channel_proto.parent_id { - insert.insert_edge(parent_id, channel.id); - } else { - insert.insert_root(channel.id); - } - } - } - } - pub fn clear(&mut self) { self.paths.clear(); self.channels_by_id.clear(); @@ -54,7 +27,7 @@ impl ChannelIndex { /// Remove the given edge from this index. This will not remove the channel /// and may result in dangling channels. - pub fn remove_edge(&mut self, parent_id: ChannelId, channel_id: ChannelId) { + pub fn delete_edge(&mut self, parent_id: ChannelId, channel_id: ChannelId) { self.paths.retain(|path| { !path .windows(2) @@ -68,8 +41,9 @@ impl ChannelIndex { self.paths.retain(|channel_path| !channel_path.iter().any(|channel_id| {channels.contains(channel_id)})) } - fn insert(& mut self) -> ChannelPathsInsertGuard { - ChannelPathsInsertGuard { + /// Upsert one or more channels into this index. + pub fn start_upsert(& mut self) -> ChannelPathsUpsertGuard { + ChannelPathsUpsertGuard { paths: &mut self.paths, channels_by_id: &mut self.channels_by_id, } @@ -86,47 +60,54 @@ impl Deref for ChannelIndex { /// A guard for ensuring that the paths index maintains its sort and uniqueness /// invariants after a series of insertions -struct ChannelPathsInsertGuard<'a> { +pub struct ChannelPathsUpsertGuard<'a> { paths: &'a mut Vec, channels_by_id: &'a mut ChannelsById, } -impl Deref for ChannelPathsInsertGuard<'_> { - type Target = ChannelsById; +impl<'a> ChannelPathsUpsertGuard<'a> { + pub fn upsert(&mut self, channel_proto: proto::Channel) { + if let Some(existing_channel) = self.channels_by_id.get_mut(&channel_proto.id) { + Arc::make_mut(existing_channel).name = channel_proto.name; - fn deref(&self) -> &Self::Target { - &self.channels_by_id + if let Some(parent_id) = channel_proto.parent_id { + self.insert_edge(parent_id, channel_proto.id) + } + } else { + let channel = Arc::new(Channel { + id: channel_proto.id, + name: channel_proto.name, + }); + self.channels_by_id.insert(channel.id, channel.clone()); + + if let Some(parent_id) = channel_proto.parent_id { + self.insert_edge(parent_id, channel.id); + } else { + self.insert_root(channel.id); + } + } } -} -impl DerefMut for ChannelPathsInsertGuard<'_> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.channels_by_id - } -} - - -impl<'a> ChannelPathsInsertGuard<'a> { - pub fn insert_edge(&mut self, parent_id: ChannelId, channel_id: ChannelId) { + fn insert_edge(&mut self, parent_id: ChannelId, channel_id: ChannelId) { let mut ix = 0; while ix < self.paths.len() { let path = &self.paths[ix]; if path.ends_with(&[parent_id]) { - let mut new_path = path.clone(); + let mut new_path = path.to_vec(); new_path.push(channel_id); - self.paths.insert(ix + 1, new_path); + self.paths.insert(ix + 1, new_path.into()); ix += 1; } ix += 1; } } - pub fn insert_root(&mut self, channel_id: ChannelId) { - self.paths.push(vec![channel_id]); + fn insert_root(&mut self, channel_id: ChannelId) { + self.paths.push(Arc::from([channel_id])); } } -impl<'a> Drop for ChannelPathsInsertGuard<'a> { +impl<'a> Drop for ChannelPathsUpsertGuard<'a> { fn drop(&mut self) { self.paths.sort_by(|a, b| { let a = channel_path_sorting_key(a, &self.channels_by_id); diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index a258151bb8..c9d5d97305 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -9,7 +9,7 @@ use crate::{ }; use anyhow::Result; use call::ActiveCall; -use channel::{Channel, ChannelEvent, ChannelId, ChannelStore}; +use channel::{Channel, ChannelEvent, ChannelId, ChannelStore, ChannelPath}; use channel_modal::ChannelModal; use client::{proto::PeerId, Client, Contact, User, UserStore}; use contact_finder::ContactFinder; @@ -40,7 +40,7 @@ use menu::{Confirm, SelectNext, SelectPrev}; use project::{Fs, Project}; use serde_derive::{Deserialize, Serialize}; use settings::SettingsStore; -use std::{borrow::Cow, mem, sync::Arc}; +use std::{borrow::Cow, mem, sync::Arc, hash::Hash}; use theme::{components::ComponentExt, IconButton}; use util::{iife, ResultExt, TryFutureExt}; use workspace::{ @@ -51,32 +51,32 @@ use workspace::{ #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] struct RemoveChannel { - channel_id: u64, + channel_id: ChannelId, } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] struct ToggleCollapse { - channel_id: u64, + location: ChannelLocation<'static>, } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] struct NewChannel { - channel_id: u64, + location: ChannelLocation<'static>, } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] struct InviteMembers { - channel_id: u64, + channel_id: ChannelId, } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] struct ManageMembers { - channel_id: u64, + channel_id: ChannelId, } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] struct RenameChannel { - channel_id: u64, + location: ChannelLocation<'static>, } #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] @@ -89,6 +89,26 @@ pub struct JoinChannelCall { pub channel_id: u64, } +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +struct OpenChannelBuffer { + channel_id: ChannelId, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +struct CopyChannel { + channel_id: ChannelId, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +struct CutChannel { + channel_id: ChannelId, +} + +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +struct PasteChannel { + channel_id: ChannelId, +} + actions!( collab_panel, [ @@ -111,12 +131,35 @@ impl_actions!( ToggleCollapse, OpenChannelNotes, JoinChannelCall, + OpenChannelBuffer, + CopyChannel, + CutChannel, + PasteChannel, ] ); const COLLABORATION_PANEL_KEY: &'static str = "CollaborationPanel"; +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub struct ChannelLocation<'a> { + channel: ChannelId, + parent: Cow<'a, ChannelPath>, +} + +impl From<(ChannelId, ChannelPath)> for ChannelLocation<'static> { + fn from(value: (ChannelId, ChannelPath)) -> Self { + ChannelLocation { channel: value.0, parent: Cow::Owned(value.1) } + } +} + +impl<'a> From<(ChannelId, &'a ChannelPath)> for ChannelLocation<'a> { + fn from(value: (ChannelId, &'a ChannelPath)) -> Self { + ChannelLocation { channel: value.0, parent: Cow::Borrowed(value.1) } + } +} + pub fn init(cx: &mut AppContext) { + settings::register::(cx); contact_finder::init(cx); channel_modal::init(cx); channel_view::init(cx); @@ -137,16 +180,37 @@ pub fn init(cx: &mut AppContext) { 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::open_channel_buffer); + + cx.add_action(|panel: &mut CollabPanel, action: &CopyChannel, _: &mut ViewContext| { + panel.copy = Some(ChannelCopy::Copy(action.channel_id)); + }); + + cx.add_action(|panel: &mut CollabPanel, action: &CutChannel, _: &mut ViewContext| { + // panel.copy = Some(ChannelCopy::Cut(action.channel_id)); + }); + + cx.add_action(|panel: &mut CollabPanel, action: &PasteChannel, cx: &mut ViewContext| { + if let Some(copy) = &panel.copy { + match copy { + ChannelCopy::Cut {..} => todo!(), + ChannelCopy::Copy(channel) => panel.channel_store.update(cx, |channel_store, cx| { + channel_store.move_channel(*channel, None, Some(action.channel_id), cx).detach_and_log_err(cx) + }), + } + } + }); + } #[derive(Debug)] pub enum ChannelEditingState { Create { - parent_id: Option, + location: Option>, pending_name: Option, }, Rename { - channel_id: u64, + location: ChannelLocation<'static>, pending_name: Option, }, } @@ -160,10 +224,19 @@ impl ChannelEditingState { } } +enum ChannelCopy { + Cut { + channel_id: u64, + parent_id: Option, + }, + Copy(u64), +} + pub struct CollabPanel { width: Option, fs: Arc, has_focus: bool, + copy: Option, pending_serialization: Task>, context_menu: ViewHandle, filter_editor: ViewHandle, @@ -179,7 +252,7 @@ pub struct CollabPanel { list_state: ListState, subscriptions: Vec, collapsed_sections: Vec
, - collapsed_channels: Vec, + collapsed_channels: Vec>, workspace: WeakViewHandle, context_menu_on_selected: bool, } @@ -187,7 +260,7 @@ pub struct CollabPanel { #[derive(Serialize, Deserialize)] struct SerializedCollabPanel { width: Option, - collapsed_channels: Option>, + collapsed_channels: Option>>, } #[derive(Debug)] @@ -231,6 +304,7 @@ enum ListEntry { Channel { channel: Arc, depth: usize, + path: Arc<[ChannelId]>, }, ChannelNotes { channel_id: ChannelId, @@ -348,10 +422,11 @@ impl CollabPanel { cx, ) } - ListEntry::Channel { channel, depth } => { + ListEntry::Channel { channel, depth, path } => { let channel_row = this.render_channel( &*channel, *depth, + path.to_owned(), &theme.collab_panel, is_selected, cx, @@ -420,6 +495,7 @@ impl CollabPanel { let mut this = Self { width: None, has_focus: false, + copy: None, fs: workspace.app_state().fs.clone(), pending_serialization: Task::ready(None), context_menu: cx.add_view(|cx| ContextMenu::new(view_id, cx)), @@ -700,7 +776,7 @@ impl CollabPanel { if matches!( state, ChannelEditingState::Create { - parent_id: None, + location: None, .. } ) { @@ -709,16 +785,18 @@ impl CollabPanel { } let mut collapse_depth = None; for mat in matches { - let (depth, channel) = + let (depth, channel, path) = channel_store.channel_at_index(mat.candidate_id).unwrap(); - if collapse_depth.is_none() && self.is_channel_collapsed(channel.id) { + let location: ChannelLocation<'_> = (channel.id, path).into(); + + if collapse_depth.is_none() && self.is_channel_collapsed(&location) { collapse_depth = Some(depth); } else if let Some(collapsed_depth) = collapse_depth { if depth > collapsed_depth { continue; } - if self.is_channel_collapsed(channel.id) { + if self.is_channel_collapsed(&location) { collapse_depth = Some(depth); } else { collapse_depth = None; @@ -726,18 +804,19 @@ impl CollabPanel { } match &self.channel_editing_state { - Some(ChannelEditingState::Create { parent_id, .. }) - if *parent_id == Some(channel.id) => + Some(ChannelEditingState::Create { location: parent_id, .. }) + if *parent_id == Some(location) => { self.entries.push(ListEntry::Channel { channel: channel.clone(), depth, + path: path.clone(), }); self.entries .push(ListEntry::ChannelEditor { depth: depth + 1 }); } - Some(ChannelEditingState::Rename { channel_id, .. }) - if *channel_id == channel.id => + Some(ChannelEditingState::Rename { location, .. }) + if location.channel == channel.id && location.parent == Cow::Borrowed(path) => { self.entries.push(ListEntry::ChannelEditor { depth }); } @@ -745,6 +824,7 @@ impl CollabPanel { self.entries.push(ListEntry::Channel { channel: channel.clone(), depth, + path: path.clone() }); } } @@ -1546,14 +1626,21 @@ impl CollabPanel { &self, channel: &Channel, depth: usize, + path: ChannelPath, theme: &theme::CollabPanel, is_selected: bool, cx: &mut ViewContext, ) -> AnyElement { let channel_id = channel.id; let has_children = self.channel_store.read(cx).has_children(channel_id); - let disclosed = - has_children.then(|| !self.collapsed_channels.binary_search(&channel_id).is_ok()); + + let disclosed = { + let location = ChannelLocation { + channel: channel_id, + parent: Cow::Borrowed(&path), + }; + has_children.then(|| !self.collapsed_channels.binary_search(&location).is_ok()) + }; let is_active = iife!({ let call_channel = ActiveCall::global(cx) @@ -1569,7 +1656,7 @@ impl CollabPanel { enum ChannelCall {} - MouseEventHandler::new::(channel.id as usize, cx, |state, cx| { + MouseEventHandler::new::(id(&path) as usize, cx, |state, cx| { let row_hovered = state.hovered(); Flex::::row() @@ -1637,8 +1724,8 @@ impl CollabPanel { ) .align_children_center() .styleable_component() - .disclosable(disclosed, Box::new(ToggleCollapse { channel_id })) - .with_id(channel_id as usize) + .disclosable(disclosed, Box::new(ToggleCollapse { location: (channel_id, path.clone()).into() })) + .with_id(id(&path) as usize) .with_style(theme.disclosure.clone()) .element() .constrained() @@ -1654,7 +1741,7 @@ impl CollabPanel { this.join_channel_chat(channel_id, cx); }) .on_click(MouseButton::Right, move |e, this, cx| { - this.deploy_channel_context_menu(Some(e.position), channel_id, cx); + this.deploy_channel_context_menu(Some(e.position), &(channel_id, path.clone()).into(), cx); }) .with_cursor_style(CursorStyle::PointingHand) .into_any() @@ -1901,7 +1988,7 @@ impl CollabPanel { fn deploy_channel_context_menu( &mut self, position: Option, - channel_id: u64, + location: &ChannelLocation<'static>, cx: &mut ViewContext, ) { self.context_menu_on_selected = position.is_none(); @@ -1913,27 +2000,29 @@ impl CollabPanel { OverlayPositionMode::Window }); - let expand_action_name = if self.is_channel_collapsed(channel_id) { + let expand_action_name = if self.is_channel_collapsed(&location) { "Expand Subchannels" } else { "Collapse Subchannels" }; let mut items = vec![ - ContextMenuItem::action(expand_action_name, ToggleCollapse { channel_id }), - ContextMenuItem::action("Open Notes", OpenChannelNotes { channel_id }), + ContextMenuItem::action(expand_action_name, ToggleCollapse { location: location.clone() }), + ContextMenuItem::action("Open Notes", OpenChannelBuffer { channel_id: location.channel }), ]; - if self.channel_store.read(cx).is_user_admin(channel_id) { + if self.channel_store.read(cx).is_user_admin(location.channel) { items.extend([ ContextMenuItem::Separator, - ContextMenuItem::action("New Subchannel", NewChannel { channel_id }), - ContextMenuItem::action("Rename", RenameChannel { channel_id }), + ContextMenuItem::action("New Subchannel", NewChannel { location: location.clone() }), + ContextMenuItem::action("Rename", RenameChannel { location: location.clone() }), + ContextMenuItem::action("Copy", CopyChannel { channel_id: location.channel }), + ContextMenuItem::action("Paste", PasteChannel { channel_id: location.channel }), ContextMenuItem::Separator, - ContextMenuItem::action("Invite Members", InviteMembers { channel_id }), - ContextMenuItem::action("Manage Members", ManageMembers { channel_id }), + ContextMenuItem::action("Invite Members", InviteMembers { channel_id: location.channel }), + ContextMenuItem::action("Manage Members", ManageMembers { channel_id: location.channel }), ContextMenuItem::Separator, - ContextMenuItem::action("Delete", RemoveChannel { channel_id }), + ContextMenuItem::action("Delete", RemoveChannel { channel_id: location.channel }), ]); } @@ -2059,7 +2148,7 @@ impl CollabPanel { if let Some(editing_state) = &mut self.channel_editing_state { match editing_state { ChannelEditingState::Create { - parent_id, + location, pending_name, .. } => { @@ -2072,13 +2161,13 @@ impl CollabPanel { self.channel_store .update(cx, |channel_store, cx| { - channel_store.create_channel(&channel_name, *parent_id, cx) + channel_store.create_channel(&channel_name, location.as_ref().map(|location| location.channel), cx) }) .detach(); cx.notify(); } ChannelEditingState::Rename { - channel_id, + location, pending_name, } => { if pending_name.is_some() { @@ -2089,7 +2178,7 @@ impl CollabPanel { self.channel_store .update(cx, |channel_store, cx| { - channel_store.rename(*channel_id, &channel_name, cx) + channel_store.rename(location.channel, &channel_name, cx) }) .detach(); cx.notify(); @@ -2116,38 +2205,42 @@ impl CollabPanel { _: &CollapseSelectedChannel, cx: &mut ViewContext, ) { - let Some(channel_id) = self.selected_channel().map(|channel| channel.id) else { + let Some((channel_id, path)) = self.selected_channel().map(|(channel, parent)| (channel.id, parent)) else { return; }; - if self.is_channel_collapsed(channel_id) { + let path = path.to_owned(); + + if self.is_channel_collapsed(&(channel_id, path.clone()).into()) { return; } - self.toggle_channel_collapsed(&ToggleCollapse { channel_id }, cx) + self.toggle_channel_collapsed(&ToggleCollapse { location: (channel_id, path).into() }, cx) } fn expand_selected_channel(&mut self, _: &ExpandSelectedChannel, cx: &mut ViewContext) { - let Some(channel_id) = self.selected_channel().map(|channel| channel.id) else { + let Some((channel_id, path)) = self.selected_channel().map(|(channel, parent)| (channel.id, parent)) else { return; }; - if !self.is_channel_collapsed(channel_id) { + let path = path.to_owned(); + + if !self.is_channel_collapsed(&(channel_id, path.clone()).into()) { return; } - self.toggle_channel_collapsed(&ToggleCollapse { channel_id }, cx) + self.toggle_channel_collapsed(&ToggleCollapse { location: (channel_id, path).into() }, cx) } fn toggle_channel_collapsed(&mut self, action: &ToggleCollapse, cx: &mut ViewContext) { - let channel_id = action.channel_id; + let location = action.location.clone(); - match self.collapsed_channels.binary_search(&channel_id) { + match self.collapsed_channels.binary_search(&location) { Ok(ix) => { self.collapsed_channels.remove(ix); } Err(ix) => { - self.collapsed_channels.insert(ix, channel_id); + self.collapsed_channels.insert(ix, location); } }; self.serialize(cx); @@ -2156,8 +2249,8 @@ impl CollabPanel { cx.focus_self(); } - fn is_channel_collapsed(&self, channel: ChannelId) -> bool { - self.collapsed_channels.binary_search(&channel).is_ok() + fn is_channel_collapsed(&self, location: &ChannelLocation) -> bool { + self.collapsed_channels.binary_search(location).is_ok() } fn leave_call(cx: &mut ViewContext) { @@ -2182,7 +2275,7 @@ impl CollabPanel { fn new_root_channel(&mut self, cx: &mut ViewContext) { self.channel_editing_state = Some(ChannelEditingState::Create { - parent_id: None, + location: None, pending_name: None, }); self.update_entries(false, cx); @@ -2200,9 +2293,9 @@ impl CollabPanel { fn new_subchannel(&mut self, action: &NewChannel, cx: &mut ViewContext) { self.collapsed_channels - .retain(|&channel| channel != action.channel_id); + .retain(|channel| *channel != action.location); self.channel_editing_state = Some(ChannelEditingState::Create { - parent_id: Some(action.channel_id), + location: Some(action.location.to_owned()), pending_name: None, }); self.update_entries(false, cx); @@ -2220,16 +2313,16 @@ impl CollabPanel { } fn remove(&mut self, _: &Remove, cx: &mut ViewContext) { - if let Some(channel) = self.selected_channel() { + if let Some((channel, _)) = self.selected_channel() { self.remove_channel(channel.id, cx) } } fn rename_selected_channel(&mut self, _: &menu::SecondaryConfirm, cx: &mut ViewContext) { - if let Some(channel) = self.selected_channel() { + if let Some((channel, parent)) = self.selected_channel() { self.rename_channel( &RenameChannel { - channel_id: channel.id, + location: (channel.id, parent.to_owned()).into(), }, cx, ); @@ -2238,12 +2331,12 @@ impl CollabPanel { fn rename_channel(&mut self, action: &RenameChannel, cx: &mut ViewContext) { let channel_store = self.channel_store.read(cx); - if !channel_store.is_user_admin(action.channel_id) { + if !channel_store.is_user_admin(action.location.channel) { return; } - if let Some(channel) = channel_store.channel_for_id(action.channel_id).cloned() { + if let Some(channel) = channel_store.channel_for_id(action.location.channel).cloned() { self.channel_editing_state = Some(ChannelEditingState::Rename { - channel_id: action.channel_id, + location: action.location.to_owned(), pending_name: None, }); self.channel_name_editor.update(cx, |editor, cx| { @@ -2263,18 +2356,18 @@ impl CollabPanel { } fn show_inline_context_menu(&mut self, _: &menu::ShowContextMenu, cx: &mut ViewContext) { - let Some(channel) = self.selected_channel() else { + let Some((channel, path)) = self.selected_channel() else { return; }; - self.deploy_channel_context_menu(None, channel.id, cx); + self.deploy_channel_context_menu(None, &(channel.id, path.to_owned()).into(), cx); } - fn selected_channel(&self) -> Option<&Arc> { + fn selected_channel(&self) -> Option<(&Arc, &ChannelPath)> { self.selection .and_then(|ix| self.entries.get(ix)) .and_then(|entry| match entry { - ListEntry::Channel { channel, .. } => Some(channel), + ListEntry::Channel { channel, path: parent, .. } => Some((channel, parent)), _ => None, }) } @@ -2657,13 +2750,15 @@ impl PartialEq for ListEntry { ListEntry::Channel { channel: channel_1, depth: depth_1, + path: parent_1, } => { if let ListEntry::Channel { channel: channel_2, depth: depth_2, + path: parent_2, } = other { - return channel_1.id == channel_2.id && depth_1 == depth_2; + return channel_1.id == channel_2.id && depth_1 == depth_2 && parent_1 == parent_2; } } ListEntry::ChannelNotes { channel_id } => { @@ -2726,3 +2821,26 @@ fn render_icon_button(style: &IconButton, svg_path: &'static str) -> impl Elemen .contained() .with_style(style.container) } + +/// Hash a channel path to a u64, for use as a mouse id +/// Based on the Fowler–Noll–Vo hash: +/// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function +fn id(path: &[ChannelId]) -> u64 { + // I probably should have done this, but I didn't + // let hasher = DefaultHasher::new(); + // let path = path.hash(&mut hasher); + // let x = hasher.finish(); + + const OFFSET: u64 = 14695981039346656037; + const PRIME: u64 = 1099511628211; + + let mut hash = OFFSET; + for id in path.iter() { + for id in id.to_ne_bytes() { + hash = hash ^ (id as u64); + hash = (hash as u128 * PRIME as u128) as u64; + } + } + + hash +} diff --git a/crates/rpc/src/proto.rs b/crates/rpc/src/proto.rs index 121f49b966..ccff1526f0 100644 --- a/crates/rpc/src/proto.rs +++ b/crates/rpc/src/proto.rs @@ -328,7 +328,6 @@ request_messages!( (GetChannelMessages, GetChannelMessagesResponse), (GetChannelMembers, GetChannelMembersResponse), (JoinChannel, JoinRoomResponse), - (RemoveChannel, Ack), (RemoveChannelMessage, Ack), (DeleteChannel, Ack), (RenameProjectEntry, ProjectEntryResponse),