diff --git a/crates/call/src/call.rs b/crates/call/src/call.rs index 11fc549084..8c9ff71ba4 100644 --- a/crates/call/src/call.rs +++ b/crates/call/src/call.rs @@ -5,7 +5,7 @@ pub mod room; use anyhow::{anyhow, Result}; use audio::Audio; use call_settings::CallSettings; -use client::{proto, Client, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE}; +use client::{proto, ChannelId, Client, TypedEnvelope, User, UserStore, ZED_ALWAYS_ACTIVE}; use collections::HashSet; use futures::{channel::oneshot, future::Shared, Future, FutureExt}; use gpui::{ @@ -107,7 +107,7 @@ impl ActiveCall { } } - pub fn channel_id(&self, cx: &AppContext) -> Option { + pub fn channel_id(&self, cx: &AppContext) -> Option { self.room()?.read(cx).channel_id() } @@ -336,7 +336,7 @@ impl ActiveCall { pub fn join_channel( &mut self, - channel_id: u64, + channel_id: ChannelId, cx: &mut ModelContext, ) -> Task>>> { if let Some(room) = self.room().cloned() { @@ -487,7 +487,7 @@ impl ActiveCall { pub fn report_call_event_for_room( operation: &'static str, room_id: u64, - channel_id: Option, + channel_id: Option, client: &Arc, ) { let telemetry = client.telemetry(); @@ -497,7 +497,7 @@ pub fn report_call_event_for_room( pub fn report_call_event_for_channel( operation: &'static str, - channel_id: u64, + channel_id: ChannelId, client: &Arc, cx: &AppContext, ) { diff --git a/crates/call/src/room.rs b/crates/call/src/room.rs index 73577ffd1e..3e524f5b3e 100644 --- a/crates/call/src/room.rs +++ b/crates/call/src/room.rs @@ -6,7 +6,7 @@ use anyhow::{anyhow, Result}; use audio::{Audio, Sound}; use client::{ proto::{self, PeerId}, - Client, ParticipantIndex, TypedEnvelope, User, UserStore, + ChannelId, Client, ParticipantIndex, TypedEnvelope, User, UserStore, }; use collections::{BTreeMap, HashMap, HashSet}; use fs::Fs; @@ -27,7 +27,7 @@ pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); #[derive(Clone, Debug, PartialEq, Eq)] pub enum Event { RoomJoined { - channel_id: Option, + channel_id: Option, }, ParticipantLocationChanged { participant_id: proto::PeerId, @@ -53,13 +53,13 @@ pub enum Event { project_id: u64, }, Left { - channel_id: Option, + channel_id: Option, }, } pub struct Room { id: u64, - channel_id: Option, + channel_id: Option, live_kit: Option, status: RoomStatus, shared_projects: HashSet>, @@ -84,7 +84,7 @@ pub struct Room { impl EventEmitter for Room {} impl Room { - pub fn channel_id(&self) -> Option { + pub fn channel_id(&self) -> Option { self.channel_id } @@ -106,7 +106,7 @@ impl Room { fn new( id: u64, - channel_id: Option, + channel_id: Option, live_kit_connection_info: Option, client: Arc, user_store: Model, @@ -273,13 +273,17 @@ impl Room { } pub(crate) async fn join_channel( - channel_id: u64, + channel_id: ChannelId, client: Arc, user_store: Model, cx: AsyncAppContext, ) -> Result> { Self::from_join_response( - client.request(proto::JoinChannel { channel_id }).await?, + client + .request(proto::JoinChannel { + channel_id: channel_id.0, + }) + .await?, client, user_store, cx, @@ -337,7 +341,7 @@ impl Room { let room = cx.new_model(|cx| { Self::new( room_proto.id, - response.channel_id, + response.channel_id.map(ChannelId), response.live_kit_connection_info, client, user_store, diff --git a/crates/channel/src/channel.rs b/crates/channel/src/channel.rs index f38ae4078a..1dbf502252 100644 --- a/crates/channel/src/channel.rs +++ b/crates/channel/src/channel.rs @@ -11,7 +11,7 @@ pub use channel_chat::{ mentions_to_proto, ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId, MessageParams, }; -pub use channel_store::{Channel, ChannelEvent, ChannelId, ChannelMembership, ChannelStore}; +pub use channel_store::{Channel, ChannelEvent, ChannelMembership, ChannelStore, HostedProjectId}; #[cfg(test)] mod channel_store_tests; diff --git a/crates/channel/src/channel_buffer.rs b/crates/channel/src/channel_buffer.rs index dc63c55d15..8e63e347f1 100644 --- a/crates/channel/src/channel_buffer.rs +++ b/crates/channel/src/channel_buffer.rs @@ -1,6 +1,6 @@ -use crate::{Channel, ChannelId, ChannelStore}; +use crate::{Channel, ChannelStore}; use anyhow::Result; -use client::{Client, Collaborator, UserStore, ZED_ALWAYS_ACTIVE}; +use client::{ChannelId, Client, Collaborator, UserStore, ZED_ALWAYS_ACTIVE}; use collections::HashMap; use gpui::{AppContext, AsyncAppContext, Context, EventEmitter, Model, ModelContext, Task}; use language::proto::serialize_version; @@ -51,7 +51,7 @@ impl ChannelBuffer { ) -> Result> { let response = client .request(proto::JoinChannelBuffer { - channel_id: channel.id, + channel_id: channel.id.0, }) .await?; let buffer_id = BufferId::new(response.buffer_id)?; @@ -68,7 +68,7 @@ impl ChannelBuffer { })?; buffer.update(&mut cx, |buffer, cx| buffer.apply_ops(operations, cx))??; - let subscription = client.subscribe_to_entity(channel.id)?; + let subscription = client.subscribe_to_entity(channel.id.0)?; anyhow::Ok(cx.new_model(|cx| { cx.subscribe(&buffer, Self::on_buffer_update).detach(); @@ -97,7 +97,7 @@ impl ChannelBuffer { } self.client .send(proto::LeaveChannelBuffer { - channel_id: self.channel_id, + channel_id: self.channel_id.0, }) .log_err(); } @@ -191,7 +191,7 @@ impl ChannelBuffer { let operation = language::proto::serialize_operation(operation); self.client .send(proto::UpdateChannelBuffer { - channel_id: self.channel_id, + channel_id: self.channel_id.0, operations: vec![operation], }) .log_err(); diff --git a/crates/channel/src/channel_chat.rs b/crates/channel/src/channel_chat.rs index e6ed013ade..5313b34d51 100644 --- a/crates/channel/src/channel_chat.rs +++ b/crates/channel/src/channel_chat.rs @@ -1,9 +1,9 @@ -use crate::{Channel, ChannelId, ChannelStore}; +use crate::{Channel, ChannelStore}; use anyhow::{anyhow, Result}; use client::{ proto, user::{User, UserStore}, - Client, Subscription, TypedEnvelope, UserId, + ChannelId, Client, Subscription, TypedEnvelope, UserId, }; use collections::HashSet; use futures::lock::Mutex; @@ -104,10 +104,12 @@ impl ChannelChat { mut cx: AsyncAppContext, ) -> Result> { let channel_id = channel.id; - let subscription = client.subscribe_to_entity(channel_id).unwrap(); + let subscription = client.subscribe_to_entity(channel_id.0).unwrap(); let response = client - .request(proto::JoinChannelChat { channel_id }) + .request(proto::JoinChannelChat { + channel_id: channel_id.0, + }) .await?; let handle = cx.new_model(|cx| { @@ -143,7 +145,7 @@ impl ChannelChat { fn release(&mut self, _: &mut AppContext) { self.rpc .send(proto::LeaveChannelChat { - channel_id: self.channel_id, + channel_id: self.channel_id.0, }) .log_err(); } @@ -200,7 +202,7 @@ impl ChannelChat { Ok(cx.spawn(move |this, mut cx| async move { let outgoing_message_guard = outgoing_messages_lock.lock().await; let request = rpc.request(proto::SendChannelMessage { - channel_id, + channel_id: channel_id.0, body: message.text, nonce: Some(nonce.into()), mentions: mentions_to_proto(&message.mentions), @@ -220,7 +222,7 @@ impl ChannelChat { pub fn remove_message(&mut self, id: u64, cx: &mut ModelContext) -> Task> { let response = self.rpc.request(proto::RemoveChannelMessage { - channel_id: self.channel_id, + channel_id: self.channel_id.0, message_id: id, }); cx.spawn(move |this, mut cx| async move { @@ -245,7 +247,7 @@ impl ChannelChat { async move { let response = rpc .request(proto::GetChannelMessages { - channel_id, + channel_id: channel_id.0, before_message_id, }) .await?; @@ -323,7 +325,7 @@ impl ChannelChat { { self.rpc .send(proto::AckChannelMessage { - channel_id: self.channel_id, + channel_id: self.channel_id.0, message_id: latest_message_id, }) .ok(); @@ -401,7 +403,11 @@ impl ChannelChat { let channel_id = self.channel_id; cx.spawn(move |this, mut cx| { async move { - let response = rpc.request(proto::JoinChannelChat { channel_id }).await?; + let response = rpc + .request(proto::JoinChannelChat { + channel_id: channel_id.0, + }) + .await?; Self::handle_loaded_messages( this.clone(), user_store.clone(), @@ -418,7 +424,7 @@ impl ChannelChat { for pending_message in pending_messages { let request = rpc.request(proto::SendChannelMessage { - channel_id, + channel_id: channel_id.0, body: pending_message.body, mentions: mentions_to_proto(&pending_message.mentions), nonce: Some(pending_message.nonce.into()), @@ -461,7 +467,7 @@ impl ChannelChat { if self.acknowledged_message_ids.insert(id) { self.rpc .send(proto::AckChannelMessage { - channel_id: self.channel_id, + channel_id: self.channel_id.0, message_id: id, }) .ok(); diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index b2d060334b..63b4fba109 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -3,7 +3,7 @@ mod channel_index; use crate::{channel_buffer::ChannelBuffer, channel_chat::ChannelChat, ChannelMessage}; use anyhow::{anyhow, Result}; use channel_index::ChannelIndex; -use client::{Client, Subscription, User, UserId, UserStore}; +use client::{ChannelId, Client, Subscription, User, UserId, UserStore}; use collections::{hash_map, HashMap, HashSet}; use futures::{channel::mpsc, future::Shared, Future, FutureExt, StreamExt}; use gpui::{ @@ -19,15 +19,16 @@ use rpc::{ use std::{mem, sync::Arc, time::Duration}; use util::{async_maybe, maybe, ResultExt}; +pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); + pub fn init(client: &Arc, user_store: Model, cx: &mut AppContext) { let channel_store = cx.new_model(|cx| ChannelStore::new(client.clone(), user_store.clone(), cx)); cx.set_global(GlobalChannelStore(channel_store)); } -pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30); - -pub type ChannelId = u64; +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +pub struct HostedProjectId(pub u64); #[derive(Debug, Clone, Default)] struct NotesVersion { @@ -35,11 +36,31 @@ struct NotesVersion { version: clock::Global, } +#[derive(Debug, Clone)] +pub struct HostedProject { + id: HostedProjectId, + channel_id: ChannelId, + name: SharedString, + _visibility: proto::ChannelVisibility, +} + +impl From for HostedProject { + fn from(project: proto::HostedProject) -> Self { + Self { + id: HostedProjectId(project.id), + channel_id: ChannelId(project.channel_id), + _visibility: project.visibility(), + name: project.name.into(), + } + } +} + pub struct ChannelStore { pub channel_index: ChannelIndex, channel_invitations: Vec>, channel_participants: HashMap>>, channel_states: HashMap, + hosted_projects: HashMap, outgoing_invites: HashSet<(ChannelId, UserId)>, update_channels_tx: mpsc::UnboundedSender, @@ -58,7 +79,7 @@ pub struct Channel { pub id: ChannelId, pub name: SharedString, pub visibility: proto::ChannelVisibility, - pub parent_path: Vec, + pub parent_path: Vec, } #[derive(Default)] @@ -68,6 +89,7 @@ pub struct ChannelState { observed_chat_message: Option, observed_notes_versions: Option, role: Option, + projects: HashSet, } impl Channel { @@ -92,10 +114,7 @@ impl Channel { } pub fn root_id(&self) -> ChannelId { - self.parent_path - .first() - .map(|id| *id as ChannelId) - .unwrap_or(self.id) + self.parent_path.first().copied().unwrap_or(self.id) } pub fn slug(str: &str) -> String { @@ -199,6 +218,7 @@ impl ChannelStore { channel_invitations: Vec::default(), channel_index: ChannelIndex::default(), channel_participants: Default::default(), + hosted_projects: Default::default(), outgoing_invites: Default::default(), opened_buffers: Default::default(), opened_chats: Default::default(), @@ -285,6 +305,19 @@ impl ChannelStore { self.channel_index.by_id().get(&channel_id) } + pub fn projects_for_id(&self, channel_id: ChannelId) -> Vec<(SharedString, HostedProjectId)> { + let mut projects: Vec<(SharedString, HostedProjectId)> = self + .channel_states + .get(&channel_id) + .map(|state| state.projects.clone()) + .unwrap_or_default() + .into_iter() + .flat_map(|id| Some((self.hosted_projects.get(&id)?.name.clone(), id))) + .collect(); + projects.sort(); + projects + } + pub fn has_open_channel_buffer(&self, channel_id: ChannelId, _cx: &AppContext) -> bool { if let Some(buffer) = self.opened_buffers.get(&channel_id) { if let OpenedModelHandle::Open(buffer) = buffer { @@ -562,13 +595,16 @@ impl ChannelStore { let name = name.trim_start_matches("#").to_owned(); cx.spawn(move |this, mut cx| async move { let response = client - .request(proto::CreateChannel { name, parent_id }) + .request(proto::CreateChannel { + name, + parent_id: parent_id.map(|cid| cid.0), + }) .await?; let channel = response .channel .ok_or_else(|| anyhow!("missing channel in response"))?; - let channel_id = channel.id; + let channel_id = ChannelId(channel.id); this.update(&mut cx, |this, cx| { let task = this.update_channels( @@ -600,7 +636,10 @@ impl ChannelStore { let client = self.client.clone(); cx.spawn(move |_, _| async move { let _ = client - .request(proto::MoveChannel { channel_id, to }) + .request(proto::MoveChannel { + channel_id: channel_id.0, + to: to.0, + }) .await?; Ok(()) @@ -617,7 +656,7 @@ impl ChannelStore { cx.spawn(move |_, _| async move { let _ = client .request(proto::SetChannelVisibility { - channel_id, + channel_id: channel_id.0, visibility: visibility.into(), }) .await?; @@ -642,7 +681,7 @@ impl ChannelStore { cx.spawn(move |this, mut cx| async move { let result = client .request(proto::InviteChannelMember { - channel_id, + channel_id: channel_id.0, user_id, role: role.into(), }) @@ -674,7 +713,7 @@ impl ChannelStore { cx.spawn(move |this, mut cx| async move { let result = client .request(proto::RemoveChannelMember { - channel_id, + channel_id: channel_id.0, user_id, }) .await; @@ -704,7 +743,7 @@ impl ChannelStore { cx.spawn(move |this, mut cx| async move { let result = client .request(proto::SetChannelMemberRole { - channel_id, + channel_id: channel_id.0, user_id, role: role.into(), }) @@ -730,7 +769,10 @@ impl ChannelStore { let name = new_name.to_string(); cx.spawn(move |this, mut cx| async move { let channel = client - .request(proto::RenameChannel { channel_id, name }) + .request(proto::RenameChannel { + channel_id: channel_id.0, + name, + }) .await? .channel .ok_or_else(|| anyhow!("missing channel in response"))?; @@ -763,7 +805,10 @@ impl ChannelStore { let client = self.client.clone(); cx.background_executor().spawn(async move { client - .request(proto::RespondToChannelInvite { channel_id, accept }) + .request(proto::RespondToChannelInvite { + channel_id: channel_id.0, + accept, + }) .await?; Ok(()) }) @@ -778,7 +823,9 @@ impl ChannelStore { let user_store = self.user_store.downgrade(); cx.spawn(move |_, mut cx| async move { let response = client - .request(proto::GetChannelMembers { channel_id }) + .request(proto::GetChannelMembers { + channel_id: channel_id.0, + }) .await?; let user_ids = response.members.iter().map(|m| m.user_id).collect(); @@ -806,7 +853,11 @@ impl ChannelStore { pub fn remove_channel(&self, channel_id: ChannelId) -> impl Future> { let client = self.client.clone(); async move { - client.request(proto::DeleteChannel { channel_id }).await?; + client + .request(proto::DeleteChannel { + channel_id: channel_id.0, + }) + .await?; Ok(()) } } @@ -843,19 +894,23 @@ impl ChannelStore { for buffer_version in message.payload.observed_channel_buffer_version { let version = language::proto::deserialize_version(&buffer_version.version); this.acknowledge_notes_version( - buffer_version.channel_id, + ChannelId(buffer_version.channel_id), buffer_version.epoch, &version, cx, ); } for message_id in message.payload.observed_channel_message_id { - this.acknowledge_message_id(message_id.channel_id, message_id.message_id, cx); + this.acknowledge_message_id( + ChannelId(message_id.channel_id), + message_id.message_id, + cx, + ); } for membership in message.payload.channel_memberships { if let Some(role) = ChannelRole::from_i32(membership.role) { this.channel_states - .entry(membership.channel_id) + .entry(ChannelId(membership.channel_id)) .or_insert_with(|| ChannelState::default()) .set_role(role) } @@ -888,7 +943,7 @@ impl ChannelStore { let channel_buffer = buffer.read(cx); let buffer = channel_buffer.buffer().read(cx); buffer_versions.push(proto::ChannelBufferVersion { - channel_id: channel_buffer.channel_id, + channel_id: channel_buffer.channel_id.0, epoch: channel_buffer.epoch(), version: language::proto::serialize_version(&buffer.version()), }); @@ -919,7 +974,7 @@ impl ChannelStore { if let Some(remote_buffer) = response .buffers .iter_mut() - .find(|buffer| buffer.channel_id == channel_id) + .find(|buffer| buffer.channel_id == channel_id.0) { let channel_id = channel_buffer.channel_id; let remote_version = @@ -955,7 +1010,7 @@ impl ChannelStore { { client .send(proto::UpdateChannelBuffer { - channel_id, + channel_id: channel_id.0, operations: chunk, }) .ok(); @@ -1010,12 +1065,12 @@ impl ChannelStore { ) -> Option>> { if !payload.remove_channel_invitations.is_empty() { self.channel_invitations - .retain(|channel| !payload.remove_channel_invitations.contains(&channel.id)); + .retain(|channel| !payload.remove_channel_invitations.contains(&channel.id.0)); } for channel in payload.channel_invitations { match self .channel_invitations - .binary_search_by_key(&channel.id, |c| c.id) + .binary_search_by_key(&channel.id, |c| c.id.0) { Ok(ix) => { Arc::make_mut(&mut self.channel_invitations[ix]).name = channel.name.into() @@ -1023,10 +1078,14 @@ impl ChannelStore { Err(ix) => self.channel_invitations.insert( ix, Arc::new(Channel { - id: channel.id, + id: ChannelId(channel.id), visibility: channel.visibility(), name: channel.name.into(), - parent_path: channel.parent_path, + parent_path: channel + .parent_path + .into_iter() + .map(|cid| ChannelId(cid)) + .collect(), }), ), } @@ -1035,20 +1094,27 @@ impl ChannelStore { let channels_changed = !payload.channels.is_empty() || !payload.delete_channels.is_empty() || !payload.latest_channel_message_ids.is_empty() - || !payload.latest_channel_buffer_versions.is_empty(); + || !payload.latest_channel_buffer_versions.is_empty() + || !payload.hosted_projects.is_empty() + || !payload.deleted_hosted_projects.is_empty(); if channels_changed { if !payload.delete_channels.is_empty() { - self.channel_index.delete_channels(&payload.delete_channels); + let delete_channels: Vec = payload + .delete_channels + .into_iter() + .map(|cid| ChannelId(cid)) + .collect(); + self.channel_index.delete_channels(&delete_channels); self.channel_participants - .retain(|channel_id, _| !&payload.delete_channels.contains(channel_id)); + .retain(|channel_id, _| !delete_channels.contains(&channel_id)); - for channel_id in &payload.delete_channels { + for channel_id in &delete_channels { let channel_id = *channel_id; if payload .channels .iter() - .any(|channel| channel.id == channel_id) + .any(|channel| channel.id == channel_id.0) { continue; } @@ -1064,7 +1130,7 @@ impl ChannelStore { let mut index = self.channel_index.bulk_insert(); for channel in payload.channels { - let id = channel.id; + let id = ChannelId(channel.id); let channel_changed = index.insert(channel); if channel_changed { @@ -1079,17 +1145,45 @@ impl ChannelStore { for latest_buffer_version in payload.latest_channel_buffer_versions { let version = language::proto::deserialize_version(&latest_buffer_version.version); self.channel_states - .entry(latest_buffer_version.channel_id) + .entry(ChannelId(latest_buffer_version.channel_id)) .or_default() .update_latest_notes_version(latest_buffer_version.epoch, &version) } for latest_channel_message in payload.latest_channel_message_ids { self.channel_states - .entry(latest_channel_message.channel_id) + .entry(ChannelId(latest_channel_message.channel_id)) .or_default() .update_latest_message_id(latest_channel_message.message_id); } + + for hosted_project in payload.hosted_projects { + let hosted_project: HostedProject = hosted_project.into(); + if let Some(old_project) = self + .hosted_projects + .insert(hosted_project.id, hosted_project.clone()) + { + self.channel_states + .entry(old_project.channel_id) + .or_default() + .remove_hosted_project(old_project.id); + } + self.channel_states + .entry(hosted_project.channel_id) + .or_default() + .add_hosted_project(hosted_project.id); + } + + for hosted_project_id in payload.deleted_hosted_projects { + let hosted_project_id = HostedProjectId(hosted_project_id); + + if let Some(old_project) = self.hosted_projects.remove(&hosted_project_id) { + self.channel_states + .entry(old_project.channel_id) + .or_default() + .remove_hosted_project(old_project.id); + } + } } cx.notify(); @@ -1129,7 +1223,7 @@ impl ChannelStore { participants.sort_by_key(|u| u.id); this.channel_participants - .insert(entry.channel_id, participants); + .insert(ChannelId(entry.channel_id), participants); } cx.notify(); @@ -1207,4 +1301,12 @@ impl ChannelState { version: version.clone(), }); } + + fn add_hosted_project(&mut self, project_id: HostedProjectId) { + self.projects.insert(project_id); + } + + fn remove_hosted_project(&mut self, project_id: HostedProjectId) { + self.projects.remove(&project_id); + } } diff --git a/crates/channel/src/channel_store/channel_index.rs b/crates/channel/src/channel_store/channel_index.rs index 7e6ddd2970..fc753f7444 100644 --- a/crates/channel/src/channel_store/channel_index.rs +++ b/crates/channel/src/channel_store/channel_index.rs @@ -1,4 +1,5 @@ -use crate::{Channel, ChannelId}; +use crate::Channel; +use client::ChannelId; use collections::BTreeMap; use rpc::proto; use std::sync::Arc; @@ -50,27 +51,32 @@ pub struct ChannelPathsInsertGuard<'a> { impl<'a> ChannelPathsInsertGuard<'a> { pub fn insert(&mut self, channel_proto: proto::Channel) -> bool { let mut ret = false; - if let Some(existing_channel) = self.channels_by_id.get_mut(&channel_proto.id) { + let parent_path = channel_proto + .parent_path + .iter() + .map(|cid| ChannelId(*cid)) + .collect(); + if let Some(existing_channel) = self.channels_by_id.get_mut(&ChannelId(channel_proto.id)) { let existing_channel = Arc::make_mut(existing_channel); ret = existing_channel.visibility != channel_proto.visibility() || existing_channel.name != channel_proto.name - || existing_channel.parent_path != channel_proto.parent_path; + || existing_channel.parent_path != parent_path; existing_channel.visibility = channel_proto.visibility(); existing_channel.name = channel_proto.name.into(); - existing_channel.parent_path = channel_proto.parent_path.into(); + existing_channel.parent_path = parent_path; } else { self.channels_by_id.insert( - channel_proto.id, + ChannelId(channel_proto.id), Arc::new(Channel { - id: channel_proto.id, + id: ChannelId(channel_proto.id), visibility: channel_proto.visibility(), name: channel_proto.name.into(), - parent_path: channel_proto.parent_path, + parent_path, }), ); - self.insert_root(channel_proto.id); + self.insert_root(ChannelId(channel_proto.id)); } ret } @@ -94,7 +100,7 @@ impl<'a> Drop for ChannelPathsInsertGuard<'a> { fn channel_path_sorting_key<'a>( id: ChannelId, channels_by_id: &'a BTreeMap>, -) -> impl Iterator { +) -> impl Iterator { let (parent_path, name) = channels_by_id .get(&id) .map_or((&[] as &[_], None), |channel| { diff --git a/crates/client/src/telemetry.rs b/crates/client/src/telemetry.rs index 0520f52988..4bddb2841b 100644 --- a/crates/client/src/telemetry.rs +++ b/crates/client/src/telemetry.rs @@ -1,6 +1,6 @@ mod event_coalescer; -use crate::TelemetrySettings; +use crate::{ChannelId, TelemetrySettings}; use chrono::{DateTime, Utc}; use clock::SystemClock; use futures::Future; @@ -278,12 +278,12 @@ impl Telemetry { self: &Arc, operation: &'static str, room_id: Option, - channel_id: Option, + channel_id: Option, ) { let event = Event::Call(CallEvent { operation: operation.to_string(), room_id, - channel_id, + channel_id: channel_id.map(|cid| cid.0), }); self.report_event(event) diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 0263d9a950..7a45ddb8f9 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -15,6 +15,15 @@ use util::TryFutureExt as _; pub type UserId = u64; +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)] +pub struct ChannelId(pub u64); + +impl std::fmt::Display for ChannelId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ParticipantIndex(pub u32); diff --git a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql index fef10f987e..20658e95cb 100644 --- a/crates/collab/migrations.sqlite/20221109000000_test_schema.sql +++ b/crates/collab/migrations.sqlite/20221109000000_test_schema.sql @@ -375,3 +375,13 @@ CREATE TABLE extension_versions ( CREATE UNIQUE INDEX "index_extensions_external_id" ON "extensions" ("external_id"); CREATE INDEX "index_extensions_total_download_count" ON "extensions" ("total_download_count"); + +CREATE TABLE hosted_projects ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + channel_id INTEGER NOT NULL REFERENCES channels(id), + name TEXT NOT NULL, + visibility TEXT NOT NULL, + deleted_at TIMESTAMP NULL +); +CREATE INDEX idx_hosted_projects_on_channel_id ON hosted_projects (channel_id); +CREATE UNIQUE INDEX uix_hosted_projects_on_channel_id_and_name ON hosted_projects (channel_id, name) WHERE (deleted_at IS NULL); diff --git a/crates/collab/migrations/20240226163408_hosted_projects.sql b/crates/collab/migrations/20240226163408_hosted_projects.sql new file mode 100644 index 0000000000..c6ade7161c --- /dev/null +++ b/crates/collab/migrations/20240226163408_hosted_projects.sql @@ -0,0 +1,11 @@ +-- Add migration script here + +CREATE TABLE hosted_projects ( + id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, + channel_id INT NOT NULL REFERENCES channels(id), + name TEXT NOT NULL, + visibility TEXT NOT NULL, + deleted_at TIMESTAMP NULL +); +CREATE INDEX idx_hosted_projects_on_channel_id ON hosted_projects (channel_id); +CREATE UNIQUE INDEX uix_hosted_projects_on_channel_id_and_name ON hosted_projects (channel_id, name) WHERE (deleted_at IS NULL); diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 7db1faea9f..6f582eda9f 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -587,6 +587,7 @@ pub struct ChannelsForUser { pub channels: Vec, pub channel_memberships: Vec, pub channel_participants: HashMap>, + pub hosted_projects: Vec, pub observed_buffer_versions: Vec, pub observed_channel_messages: Vec, diff --git a/crates/collab/src/db/ids.rs b/crates/collab/src/db/ids.rs index 093fc6d885..d552f646a0 100644 --- a/crates/collab/src/db/ids.rs +++ b/crates/collab/src/db/ids.rs @@ -88,6 +88,7 @@ id_type!(FlagId); id_type!(ExtensionId); id_type!(NotificationId); id_type!(NotificationKindId); +id_type!(HostedProjectId); /// ChannelRole gives you permissions for both channels and calls. #[derive(Eq, PartialEq, Copy, Clone, Debug, EnumIter, DeriveActiveEnum, Default, Hash)] diff --git a/crates/collab/src/db/queries.rs b/crates/collab/src/db/queries.rs index 7d9043f595..0326cf4374 100644 --- a/crates/collab/src/db/queries.rs +++ b/crates/collab/src/db/queries.rs @@ -6,6 +6,7 @@ pub mod channels; pub mod contacts; pub mod contributors; pub mod extensions; +pub mod hosted_projects; pub mod messages; pub mod notifications; pub mod projects; diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index f46c5a75ff..7a9034e8d0 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -652,9 +652,14 @@ impl Database { .observed_channel_messages(&channel_ids, user_id, &*tx) .await?; + let hosted_projects = self + .get_hosted_projects(&channel_ids, &roles_by_channel_id, &*tx) + .await?; + Ok(ChannelsForUser { channel_memberships, channels, + hosted_projects, channel_participants, latest_buffer_versions, latest_channel_messages, diff --git a/crates/collab/src/db/queries/hosted_projects.rs b/crates/collab/src/db/queries/hosted_projects.rs new file mode 100644 index 0000000000..fd9a991906 --- /dev/null +++ b/crates/collab/src/db/queries/hosted_projects.rs @@ -0,0 +1,42 @@ +use rpc::proto; + +use super::*; + +impl Database { + pub async fn get_hosted_projects( + &self, + channel_ids: &Vec, + roles: &HashMap, + tx: &DatabaseTransaction, + ) -> Result> { + Ok(hosted_project::Entity::find() + .filter(hosted_project::Column::ChannelId.is_in(channel_ids.iter().map(|id| id.0))) + .all(&*tx) + .await? + .into_iter() + .flat_map(|project| { + if project.deleted_at.is_some() { + return None; + } + match project.visibility { + ChannelVisibility::Public => {} + ChannelVisibility::Members => { + let is_visible = roles + .get(&project.channel_id) + .map(|role| role.can_see_all_descendants()) + .unwrap_or(false); + if !is_visible { + return None; + } + } + }; + Some(proto::HostedProject { + id: project.id.to_proto(), + channel_id: project.channel_id.to_proto(), + name: project.name.clone(), + visibility: project.visibility.into(), + }) + }) + .collect()) + } +} diff --git a/crates/collab/src/db/tables.rs b/crates/collab/src/db/tables.rs index 72d9835032..468e7390ab 100644 --- a/crates/collab/src/db/tables.rs +++ b/crates/collab/src/db/tables.rs @@ -14,6 +14,7 @@ pub mod extension; pub mod extension_version; pub mod feature_flag; pub mod follower; +pub mod hosted_project; pub mod language_server; pub mod notification; pub mod notification_kind; diff --git a/crates/collab/src/db/tables/hosted_project.rs b/crates/collab/src/db/tables/hosted_project.rs new file mode 100644 index 0000000000..265acd80ff --- /dev/null +++ b/crates/collab/src/db/tables/hosted_project.rs @@ -0,0 +1,18 @@ +use crate::db::{ChannelId, ChannelVisibility, HostedProjectId}; +use sea_orm::entity::prelude::*; + +#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] +#[sea_orm(table_name = "hosted_projects")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: HostedProjectId, + pub channel_id: ChannelId, + pub name: String, + pub visibility: ChannelVisibility, + pub deleted_at: Option, +} + +impl ActiveModelBehavior for ActiveModel {} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 82ed654505..28f8a204d0 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -3396,6 +3396,9 @@ fn build_channels_update( for channel in channel_invites { update.channel_invitations.push(channel.to_proto()); } + for project in channels.hosted_projects { + update.hosted_projects.push(project); + } update } diff --git a/crates/collab/src/tests.rs b/crates/collab/src/tests.rs index aca9329d5a..36d3dca711 100644 --- a/crates/collab/src/tests.rs +++ b/crates/collab/src/tests.rs @@ -1,4 +1,5 @@ use call::Room; +use client::ChannelId; use gpui::{Model, TestAppContext}; mod channel_buffer_tests; @@ -43,6 +44,6 @@ fn room_participants(room: &Model, cx: &mut TestAppContext) -> RoomPartici }) } -fn channel_id(room: &Model, cx: &mut TestAppContext) -> Option { +fn channel_id(room: &Model, cx: &mut TestAppContext) -> Option { cx.read(|cx| room.read(cx).channel_id()) } diff --git a/crates/collab/src/tests/channel_guest_tests.rs b/crates/collab/src/tests/channel_guest_tests.rs index 9599aa788c..24828e42ef 100644 --- a/crates/collab/src/tests/channel_guest_tests.rs +++ b/crates/collab/src/tests/channel_guest_tests.rs @@ -183,7 +183,7 @@ async fn test_channel_requires_zed_cla(cx_a: &mut TestAppContext, cx_b: &mut Tes server .app_state .db - .set_channel_requires_zed_cla(ChannelId::from_proto(parent_channel_id), true) + .set_channel_requires_zed_cla(ChannelId::from_proto(parent_channel_id.0), true) .await .unwrap(); diff --git a/crates/collab/src/tests/channel_message_tests.rs b/crates/collab/src/tests/channel_message_tests.rs index 081ac8d19a..18462a0e24 100644 --- a/crates/collab/src/tests/channel_message_tests.rs +++ b/crates/collab/src/tests/channel_message_tests.rs @@ -100,13 +100,13 @@ async fn test_basic_channel_messages( Notification::ChannelMessageMention { message_id, sender_id: client_a.id(), - channel_id, + channel_id: channel_id.0, } ); assert_eq!( store.notification_at(1).unwrap().notification, Notification::ChannelInvitation { - channel_id, + channel_id: channel_id.0, channel_name: "the-channel".to_string(), inviter_id: client_a.id() } diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index eda7377c77..9d2f333304 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -4,8 +4,8 @@ use crate::{ tests::{room_participants, RoomParticipants, TestServer}, }; use call::ActiveCall; -use channel::{ChannelId, ChannelMembership, ChannelStore}; -use client::User; +use channel::{ChannelMembership, ChannelStore}; +use client::{ChannelId, User}; use futures::future::try_join_all; use gpui::{BackgroundExecutor, Model, SharedString, TestAppContext}; use rpc::{ @@ -281,7 +281,7 @@ async fn test_core_channels( .app_state .db .rename_channel( - db::ChannelId::from_proto(channel_a_id), + db::ChannelId::from_proto(channel_a_id.0), UserId::from_proto(client_a.id()), "channel-a-renamed", ) @@ -1444,7 +1444,7 @@ fn assert_channels( fn assert_channels_list_shape( channel_store: &Model, cx: &TestAppContext, - expected_channels: &[(u64, usize)], + expected_channels: &[(ChannelId, usize)], ) { let actual = cx.read(|cx| { channel_store.read_with(cx, |store, _| { diff --git a/crates/collab/src/tests/following_tests.rs b/crates/collab/src/tests/following_tests.rs index acc129e675..7c18d020cb 100644 --- a/crates/collab/src/tests/following_tests.rs +++ b/crates/collab/src/tests/following_tests.rs @@ -1,5 +1,6 @@ use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer}; use call::{ActiveCall, ParticipantLocation}; +use client::ChannelId; use collab_ui::{ channel_view::ChannelView, notifications::project_shared_notification::ProjectSharedNotification, @@ -2000,7 +2001,7 @@ async fn test_following_to_channel_notes_without_a_shared_project( } async fn join_channel( - channel_id: u64, + channel_id: ChannelId, client: &TestClient, cx: &mut TestAppContext, ) -> anyhow::Result<()> { diff --git a/crates/collab/src/tests/notification_tests.rs b/crates/collab/src/tests/notification_tests.rs index f6066e6409..ddbc1d197b 100644 --- a/crates/collab/src/tests/notification_tests.rs +++ b/crates/collab/src/tests/notification_tests.rs @@ -137,7 +137,7 @@ async fn test_notifications( assert_eq!( entry.notification, Notification::ChannelInvitation { - channel_id, + channel_id: channel_id.0, channel_name: "the-channel".to_string(), inviter_id: client_a.id() } diff --git a/crates/collab/src/tests/random_channel_buffer_tests.rs b/crates/collab/src/tests/random_channel_buffer_tests.rs index f980f7d908..0eacc56ffb 100644 --- a/crates/collab/src/tests/random_channel_buffer_tests.rs +++ b/crates/collab/src/tests/random_channel_buffer_tests.rs @@ -253,7 +253,7 @@ impl RandomizedTest for RandomChannelBufferTest { .channel_buffers() .deref() .iter() - .find(|b| b.read(cx).channel_id == channel_id.to_proto()) + .find(|b| b.read(cx).channel_id.0 == channel_id.to_proto()) { let channel_buffer = channel_buffer.read(cx); diff --git a/crates/collab/src/tests/test_server.rs b/crates/collab/src/tests/test_server.rs index f79698de48..35fee85ad2 100644 --- a/crates/collab/src/tests/test_server.rs +++ b/crates/collab/src/tests/test_server.rs @@ -8,7 +8,8 @@ use anyhow::anyhow; use call::ActiveCall; use channel::{ChannelBuffer, ChannelStore}; use client::{ - self, proto::PeerId, Client, Connection, Credentials, EstablishConnectionError, UserStore, + self, proto::PeerId, ChannelId, Client, Connection, Credentials, EstablishConnectionError, + UserStore, }; use clock::FakeSystemClock; use collab_ui::channel_view::ChannelView; @@ -120,7 +121,7 @@ impl TestServer { pub async fn start2( cx_a: &mut TestAppContext, cx_b: &mut TestAppContext, - ) -> (TestServer, TestClient, TestClient, u64) { + ) -> (TestServer, TestClient, TestClient, ChannelId) { let mut server = Self::start(cx_a.executor()).await; let client_a = server.create_client(cx_a, "user_a").await; let client_b = server.create_client(cx_b, "user_b").await; @@ -353,10 +354,10 @@ impl TestServer { pub async fn make_channel( &self, channel: &str, - parent: Option, + parent: Option, admin: (&TestClient, &mut TestAppContext), members: &mut [(&TestClient, &mut TestAppContext)], - ) -> u64 { + ) -> ChannelId { let (_, admin_cx) = admin; let channel_id = admin_cx .read(ChannelStore::global) @@ -399,7 +400,7 @@ impl TestServer { channel: &str, client: &TestClient, cx: &mut TestAppContext, - ) -> u64 { + ) -> ChannelId { let channel_id = self .make_channel(channel, None, (client, cx), &mut []) .await; @@ -423,7 +424,7 @@ impl TestServer { &self, channels: &[(&str, Option<&str>)], creator: (&TestClient, &mut TestAppContext), - ) -> Vec { + ) -> Vec { let mut observed_channels = HashMap::default(); let mut result = Vec::new(); for (channel, parent) in channels { @@ -677,7 +678,7 @@ impl TestClient { pub async fn host_workspace( &self, workspace: &View, - channel_id: u64, + channel_id: ChannelId, cx: &mut VisualTestContext, ) { cx.update(|cx| { @@ -698,7 +699,7 @@ impl TestClient { pub async fn join_workspace<'a>( &'a self, - channel_id: u64, + channel_id: ChannelId, cx: &'a mut TestAppContext, ) -> (View, &'a mut VisualTestContext) { cx.update(|cx| workspace::join_channel(channel_id, self.app_state.clone(), None, cx)) @@ -777,7 +778,7 @@ impl TestClient { } pub fn open_channel_notes( - channel_id: u64, + channel_id: ChannelId, cx: &mut VisualTestContext, ) -> Task>> { let window = cx.update(|cx| cx.active_window().unwrap().downcast::().unwrap()); diff --git a/crates/collab_ui/src/channel_view.rs b/crates/collab_ui/src/channel_view.rs index 939276bf1b..f591c67f7c 100644 --- a/crates/collab_ui/src/channel_view.rs +++ b/crates/collab_ui/src/channel_view.rs @@ -1,9 +1,9 @@ use anyhow::Result; use call::report_call_event_for_channel; -use channel::{Channel, ChannelBuffer, ChannelBufferEvent, ChannelId, ChannelStore}; +use channel::{Channel, ChannelBuffer, ChannelBufferEvent, ChannelStore}; use client::{ proto::{self, PeerId}, - Collaborator, ParticipantIndex, + ChannelId, Collaborator, ParticipantIndex, }; use collections::HashMap; use editor::{ @@ -454,7 +454,7 @@ impl FollowableItem for ChannelView { Some(proto::view::Variant::ChannelView( proto::view::ChannelView { - channel_id: channel_buffer.channel_id, + channel_id: channel_buffer.channel_id.0, editor: if let Some(proto::view::Variant::Editor(proto)) = self.editor.read(cx).to_state_proto(cx) { @@ -480,7 +480,8 @@ impl FollowableItem for ChannelView { unreachable!() }; - let open = ChannelView::open_in_pane(state.channel_id, None, pane, workspace, cx); + let open = + ChannelView::open_in_pane(ChannelId(state.channel_id), None, pane, workspace, cx); Some(cx.spawn(|mut cx| async move { let this = open.await?; diff --git a/crates/collab_ui/src/chat_panel.rs b/crates/collab_ui/src/chat_panel.rs index 1c75b54ba2..fd238aba0b 100644 --- a/crates/collab_ui/src/chat_panel.rs +++ b/crates/collab_ui/src/chat_panel.rs @@ -2,7 +2,7 @@ use crate::{collab_panel, ChatPanelSettings}; use anyhow::Result; use call::{room, ActiveCall}; use channel::{ChannelChat, ChannelChatEvent, ChannelMessage, ChannelMessageId, ChannelStore}; -use client::Client; +use client::{ChannelId, Client}; use collections::HashMap; use db::kvp::KEY_VALUE_STORE; use editor::Editor; @@ -169,7 +169,7 @@ impl ChatPanel { }) } - pub fn channel_id(&self, cx: &AppContext) -> Option { + pub fn channel_id(&self, cx: &AppContext) -> Option { self.active_chat .as_ref() .map(|(chat, _)| chat.read(cx).channel_id) @@ -710,7 +710,7 @@ impl ChatPanel { pub fn select_channel( &mut self, - selected_channel_id: u64, + selected_channel_id: ChannelId, scroll_to_message_id: Option, cx: &mut ViewContext, ) -> Task> { diff --git a/crates/collab_ui/src/chat_panel/message_editor.rs b/crates/collab_ui/src/chat_panel/message_editor.rs index 69e6c78735..76c355f3f8 100644 --- a/crates/collab_ui/src/chat_panel/message_editor.rs +++ b/crates/collab_ui/src/chat_panel/message_editor.rs @@ -1,6 +1,6 @@ use anyhow::Result; -use channel::{ChannelId, ChannelMembership, ChannelStore, MessageParams}; -use client::UserId; +use channel::{ChannelMembership, ChannelStore, MessageParams}; +use client::{ChannelId, UserId}; use collections::{HashMap, HashSet}; use editor::{AnchorRangeExt, CompletionProvider, Editor, EditorElement, EditorStyle}; use fuzzy::StringMatchCandidate; @@ -131,7 +131,7 @@ impl MessageEditor { pub fn set_channel( &mut self, - channel_id: u64, + channel_id: ChannelId, channel_name: Option, cx: &mut ViewContext, ) { diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 2ead87e312..396217f6c1 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -7,8 +7,8 @@ use crate::{ CollaborationPanelSettings, }; use call::ActiveCall; -use channel::{Channel, ChannelEvent, ChannelId, ChannelStore}; -use client::{Client, Contact, User, UserStore}; +use channel::{Channel, ChannelEvent, ChannelStore, HostedProjectId}; +use client::{ChannelId, Client, Contact, User, UserStore}; use contact_finder::ContactFinder; use db::kvp::KEY_VALUE_STORE; use editor::{Editor, EditorElement, EditorStyle}; @@ -184,6 +184,10 @@ enum ListEntry { ChannelEditor { depth: usize, }, + HostedProject { + id: HostedProjectId, + name: SharedString, + }, Contact { contact: Arc, calling: bool, @@ -326,7 +330,10 @@ impl CollabPanel { panel.width = serialized_panel.width; panel.collapsed_channels = serialized_panel .collapsed_channels - .unwrap_or_else(|| Vec::new()); + .unwrap_or_else(|| Vec::new()) + .iter() + .map(|cid| ChannelId(*cid)) + .collect(); cx.notify(); }); } @@ -344,7 +351,9 @@ impl CollabPanel { COLLABORATION_PANEL_KEY.into(), serde_json::to_string(&SerializedCollabPanel { width, - collapsed_channels: Some(collapsed_channels), + collapsed_channels: Some( + collapsed_channels.iter().map(|cid| cid.0).collect(), + ), })?, ) .await?; @@ -563,6 +572,7 @@ impl CollabPanel { } } + let hosted_projects = channel_store.projects_for_id(channel.id); let has_children = channel_store .channel_at_index(mat.candidate_id + 1) .map_or(false, |next_channel| { @@ -596,6 +606,10 @@ impl CollabPanel { }); } } + + for (name, id) in hosted_projects { + self.entries.push(ListEntry::HostedProject { id, name }) + } } } @@ -1023,6 +1037,33 @@ impl CollabPanel { .tooltip(move |cx| Tooltip::text("Open Chat", cx)) } + fn render_channel_project( + &self, + id: HostedProjectId, + name: &SharedString, + is_selected: bool, + cx: &mut ViewContext, + ) -> impl IntoElement { + ListItem::new(ElementId::NamedInteger( + "channel-project".into(), + id.0 as usize, + )) + .indent_level(2) + .indent_step_size(px(20.)) + .selected(is_selected) + .on_click(cx.listener(move |_this, _, _cx| { + // todo!() + })) + .start_slot( + h_flex() + .relative() + .gap_1() + .child(IconButton::new(0, IconName::FileTree)), + ) + .child(Label::new(name.clone())) + .tooltip(move |cx| Tooltip::text("Open Project", cx)) + } + fn has_subchannels(&self, ix: usize) -> bool { self.entries.get(ix).map_or(false, |entry| { if let ListEntry::Channel { has_children, .. } = entry { @@ -1486,6 +1527,12 @@ impl CollabPanel { ListEntry::ChannelChat { channel_id } => { self.join_channel_chat(*channel_id, cx) } + ListEntry::HostedProject { + id: _id, + name: _name, + } => { + // todo!() + } ListEntry::OutgoingRequest(_) => {} ListEntry::ChannelEditor { .. } => {} @@ -1923,7 +1970,7 @@ impl CollabPanel { fn respond_to_channel_invite( &mut self, - channel_id: u64, + channel_id: ChannelId, accept: bool, cx: &mut ViewContext, ) { @@ -1942,7 +1989,7 @@ impl CollabPanel { .detach_and_prompt_err("Call failed", cx, |_, _| None); } - fn join_channel(&self, channel_id: u64, cx: &mut ViewContext) { + fn join_channel(&self, channel_id: ChannelId, cx: &mut ViewContext) { let Some(workspace) = self.workspace.upgrade() else { return; }; @@ -2089,6 +2136,10 @@ impl CollabPanel { ListEntry::ChannelChat { channel_id } => self .render_channel_chat(*channel_id, is_selected, cx) .into_any_element(), + + ListEntry::HostedProject { id, name } => self + .render_channel_project(*id, name, is_selected, cx) + .into_any_element(), } } @@ -2405,7 +2456,7 @@ impl CollabPanel { .tooltip(|cx| Tooltip::text("Accept invite", cx)), ]; - ListItem::new(("channel-invite", channel.id as usize)) + ListItem::new(("channel-invite", channel.id.0 as usize)) .selected(is_selected) .child( h_flex() @@ -2497,7 +2548,7 @@ impl CollabPanel { div() .h_6() - .id(channel_id as usize) + .id(channel_id.0 as usize) .group("") .flex() .w_full() @@ -2525,7 +2576,7 @@ impl CollabPanel { this.move_channel(dragged_channel.id, channel_id, cx); })) .child( - ListItem::new(channel_id as usize) + ListItem::new(channel_id.0 as usize) // Add one level of depth for the disclosure arrow. .indent_level(depth + 1) .indent_step_size(px(20.)) @@ -2572,7 +2623,7 @@ impl CollabPanel { ) .child( h_flex() - .id(channel_id as usize) + .id(channel_id.0 as usize) .child(Label::new(channel.name.clone())) .children(face_pile.map(|face_pile| face_pile.p_1())), ), @@ -2826,6 +2877,11 @@ impl PartialEq for ListEntry { return channel_1.id == channel_2.id; } } + ListEntry::HostedProject { id, .. } => { + if let ListEntry::HostedProject { id: other_id, .. } = other { + return id == other_id; + } + } ListEntry::ChannelNotes { channel_id } => { if let ListEntry::ChannelNotes { channel_id: other_id, diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index 54e564590e..125e8c64f3 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -1,7 +1,7 @@ -use channel::{ChannelId, ChannelMembership, ChannelStore}; +use channel::{ChannelMembership, ChannelStore}; use client::{ proto::{self, ChannelRole, ChannelVisibility}, - User, UserId, UserStore, + ChannelId, User, UserId, UserStore, }; use fuzzy::{match_strings, StringMatchCandidate}; use gpui::{ diff --git a/crates/collab_ui/src/notification_panel.rs b/crates/collab_ui/src/notification_panel.rs index 54699a3440..311481c756 100644 --- a/crates/collab_ui/src/notification_panel.rs +++ b/crates/collab_ui/src/notification_panel.rs @@ -1,7 +1,7 @@ use crate::{chat_panel::ChatPanel, NotificationPanelSettings}; use anyhow::Result; use channel::ChannelStore; -use client::{Client, Notification, User, UserStore}; +use client::{ChannelId, Client, Notification, User, UserStore}; use collections::HashMap; use db::kvp::KEY_VALUE_STORE; use futures::StreamExt; @@ -357,7 +357,7 @@ impl NotificationPanel { "{} invited you to join the #{channel_name} channel", inviter.github_login ), - needs_response: channel_store.has_channel_invitation(channel_id), + needs_response: channel_store.has_channel_invitation(ChannelId(channel_id)), actor: Some(inviter), can_navigate: false, }) @@ -368,7 +368,7 @@ impl NotificationPanel { message_id, } => { let sender = user_store.get_cached_user(sender_id)?; - let channel = channel_store.channel_for_id(channel_id)?; + let channel = channel_store.channel_for_id(ChannelId(channel_id))?; let message = self .notification_store .read(cx) @@ -432,7 +432,7 @@ impl NotificationPanel { if let Some(panel) = workspace.focus_panel::(cx) { panel.update(cx, |panel, cx| { panel - .select_channel(channel_id, Some(message_id), cx) + .select_channel(ChannelId(channel_id), Some(message_id), cx) .detach_and_log_err(cx); }); } @@ -454,7 +454,7 @@ impl NotificationPanel { panel.is_scrolled_to_bottom() && panel .active_chat() - .map_or(false, |chat| chat.read(cx).channel_id == *channel_id) + .map_or(false, |chat| chat.read(cx).channel_id.0 == *channel_id) } else { false }; diff --git a/crates/notifications/src/notification_store.rs b/crates/notifications/src/notification_store.rs index a01a1e59d8..67a1ec487a 100644 --- a/crates/notifications/src/notification_store.rs +++ b/crates/notifications/src/notification_store.rs @@ -1,6 +1,6 @@ use anyhow::{Context, Result}; use channel::{ChannelMessage, ChannelMessageId, ChannelStore}; -use client::{Client, UserStore}; +use client::{ChannelId, Client, UserStore}; use collections::HashMap; use db::smol::stream::StreamExt; use gpui::{ @@ -413,7 +413,7 @@ impl NotificationStore { Notification::ChannelInvitation { channel_id, .. } => { self.channel_store .update(cx, |store, cx| { - store.respond_to_channel_invite(channel_id, response, cx) + store.respond_to_channel_invite(ChannelId(channel_id), response, cx) }) .detach(); } diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 2ad8bd4448..6a986ff1f3 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -12,7 +12,7 @@ message Envelope { uint32 id = 1; optional uint32 responding_to = 2; optional PeerId original_sender_id = 3; - + /* When you are adding a new message type, instead of adding it in semantic order and bumping the message ID's of everything that follows, add it at the end of the @@ -56,7 +56,7 @@ message Envelope { GetDefinitionResponse get_definition_response = 33; GetTypeDefinition get_type_definition = 34; GetTypeDefinitionResponse get_type_definition_response = 35; - + GetReferences get_references = 36; GetReferencesResponse get_references_response = 37; GetDocumentHighlights get_document_highlights = 38; @@ -192,7 +192,7 @@ message Envelope { LspExtExpandMacroResponse lsp_ext_expand_macro_response = 155; SetRoomParticipantRole set_room_participant_role = 156; - UpdateUserChannels update_user_channels = 157; + UpdateUserChannels update_user_channels = 157; GetImplementation get_implementation = 162; GetImplementationResponse get_implementation_response = 163; @@ -1026,6 +1026,9 @@ message UpdateChannels { repeated ChannelParticipants channel_participants = 7; repeated ChannelMessageId latest_channel_message_ids = 8; repeated ChannelBufferVersion latest_channel_buffer_versions = 9; + + repeated HostedProject hosted_projects = 10; + repeated uint64 deleted_hosted_projects = 11; } message UpdateUserChannels { @@ -1054,6 +1057,13 @@ message ChannelParticipants { repeated uint64 participant_user_ids = 2; } +message HostedProject { + uint64 id = 1; + uint64 channel_id = 2; + string name = 3; + ChannelVisibility visibility = 4; +} + message JoinChannel { uint64 channel_id = 1; } diff --git a/crates/workspace/src/workspace.rs b/crates/workspace/src/workspace.rs index 3a0d90118c..2a937d9d33 100644 --- a/crates/workspace/src/workspace.rs +++ b/crates/workspace/src/workspace.rs @@ -15,7 +15,7 @@ use anyhow::{anyhow, Context as _, Result}; use call::{call_settings::CallSettings, ActiveCall}; use client::{ proto::{self, ErrorCode, PeerId}, - Client, ErrorExt, Status, TypedEnvelope, UserStore, + ChannelId, Client, ErrorExt, Status, TypedEnvelope, UserStore, }; use collections::{hash_map, HashMap, HashSet}; use derive_more::{Deref, DerefMut}; @@ -4117,7 +4117,7 @@ pub async fn last_opened_workspace_paths() -> Option { actions!(collab, [OpenChannelNotes]); async fn join_channel_internal( - channel_id: u64, + channel_id: ChannelId, app_state: &Arc, requesting_window: Option>, active_call: &Model, @@ -4257,7 +4257,7 @@ async fn join_channel_internal( } pub fn join_channel( - channel_id: u64, + channel_id: ChannelId, app_state: Arc, requesting_window: Option>, cx: &mut AppContext, diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 96714368af..9018088ad3 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -323,8 +323,10 @@ fn main() { cx.spawn(|cx| async move { // ignore errors here, we'll show a generic "not signed in" let _ = authenticate(client, &cx).await; - cx.update(|cx| workspace::join_channel(channel_id, app_state, None, cx))? - .await?; + cx.update(|cx| { + workspace::join_channel(client::ChannelId(channel_id), app_state, None, cx) + })? + .await?; anyhow::Ok(()) }) .detach_and_log_err(cx); @@ -343,7 +345,7 @@ fn main() { workspace::get_any_active_workspace(app_state, cx.clone()).await?; let workspace = workspace_window.root_view(&cx)?; cx.update_window(workspace_window.into(), |_, cx| { - ChannelView::open(channel_id, heading, workspace, cx) + ChannelView::open(client::ChannelId(channel_id), heading, workspace, cx) })? .await?; anyhow::Ok(()) @@ -378,7 +380,12 @@ fn main() { cx.update(|mut cx| { cx.spawn(|cx| async move { cx.update(|cx| { - workspace::join_channel(channel_id, app_state, None, cx) + workspace::join_channel( + client::ChannelId(channel_id), + app_state, + None, + cx, + ) })? .await?; anyhow::Ok(()) @@ -397,7 +404,12 @@ fn main() { workspace::get_any_active_workspace(app_state, cx.clone()).await?; let workspace = workspace_window.root_view(&cx)?; cx.update_window(workspace_window.into(), |_, cx| { - ChannelView::open(channel_id, heading, workspace, cx) + ChannelView::open( + client::ChannelId(channel_id), + heading, + workspace, + cx, + ) })? .await?; anyhow::Ok(())