From 57b5bff29918a70cbb5cdbb6afece7b0484941c3 Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 16 May 2024 20:02:25 -0600 Subject: [PATCH] Support very large channel membership lists (#11939) Fixes the channel membership dialogue for the zed channel by not downloading all 111k people in one go. Release Notes: - N/A --- crates/channel/src/channel_store.rs | 39 +++--- crates/client/src/client.rs | 2 +- crates/client/src/user.rs | 33 ++--- crates/collab/src/db.rs | 3 +- crates/collab/src/db/queries/buffers.rs | 21 +-- crates/collab/src/db/queries/channels.rs | 114 ++++++++-------- crates/collab/src/db/queries/messages.rs | 8 +- crates/collab/src/db/tests/channel_tests.rs | 50 +++---- crates/collab/src/rpc.rs | 99 ++++++++------ crates/collab/src/tests/channel_tests.rs | 2 +- crates/collab_ui/src/collab_panel.rs | 30 +++-- .../src/collab_panel/channel_modal.rs | 127 +++++++++--------- crates/rpc/proto/zed.proto | 3 + 13 files changed, 270 insertions(+), 261 deletions(-) diff --git a/crates/channel/src/channel_store.rs b/crates/channel/src/channel_store.rs index 7b07c7a530..74ff7c731e 100644 --- a/crates/channel/src/channel_store.rs +++ b/crates/channel/src/channel_store.rs @@ -123,6 +123,7 @@ impl Channel { } } +#[derive(Debug)] pub struct ChannelMembership { pub user: Arc, pub kind: proto::channel_member::Kind, @@ -815,9 +816,11 @@ impl ChannelStore { Ok(()) }) } - pub fn get_channel_member_details( + pub fn fuzzy_search_members( &self, channel_id: ChannelId, + query: String, + limit: u16, cx: &mut ModelContext, ) -> Task>> { let client = self.client.clone(); @@ -826,26 +829,24 @@ impl ChannelStore { let response = client .request(proto::GetChannelMembers { channel_id: channel_id.0, + query, + limit: limit as u64, }) .await?; - - let user_ids = response.members.iter().map(|m| m.user_id).collect(); - let user_store = user_store - .upgrade() - .ok_or_else(|| anyhow!("user store dropped"))?; - let users = user_store - .update(&mut cx, |user_store, cx| user_store.get_users(user_ids, cx))? - .await?; - - Ok(users - .into_iter() - .zip(response.members) - .map(|(user, member)| ChannelMembership { - user, - role: member.role(), - kind: member.kind(), - }) - .collect()) + user_store.update(&mut cx, |user_store, _| { + user_store.insert(response.users); + response + .members + .into_iter() + .filter_map(|member| { + Some(ChannelMembership { + user: user_store.get_cached_user(member.user_id)?, + role: member.role(), + kind: member.kind(), + }) + }) + .collect() + }) }) } diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 9e31d25f30..c9e61a64f3 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -85,7 +85,7 @@ lazy_static! { } pub const INITIAL_RECONNECTION_DELAY: Duration = Duration::from_millis(100); -pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(5); +pub const CONNECTION_TIMEOUT: Duration = Duration::from_secs(20); actions!(client, [SignIn, SignOut, Reconnect]); diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs index 91fa16f157..f97f45abe9 100644 --- a/crates/client/src/user.rs +++ b/crates/client/src/user.rs @@ -670,28 +670,31 @@ impl UserStore { cx.spawn(|this, mut cx| async move { if let Some(rpc) = client.upgrade() { let response = rpc.request(request).await.context("error loading users")?; - let users = response - .users - .into_iter() - .map(User::new) - .collect::>(); + let users = response.users; - this.update(&mut cx, |this, _| { - for user in &users { - this.users.insert(user.id, user.clone()); - this.by_github_login - .insert(user.github_login.clone(), user.id); - } - }) - .ok(); - - Ok(users) + this.update(&mut cx, |this, _| this.insert(users)) } else { Ok(Vec::new()) } }) } + pub fn insert(&mut self, users: Vec) -> Vec> { + let mut ret = Vec::with_capacity(users.len()); + for user in users { + let user = User::new(user); + if let Some(old) = self.users.insert(user.id, user.clone()) { + if old.github_login != user.github_login { + self.by_github_login.remove(&old.github_login); + } + } + self.by_github_login + .insert(user.github_login.clone(), user.id); + ret.push(user) + } + ret + } + pub fn set_participant_indices( &mut self, participant_indices: HashMap, diff --git a/crates/collab/src/db.rs b/crates/collab/src/db.rs index 94ccaffb97..02b182aca7 100644 --- a/crates/collab/src/db.rs +++ b/crates/collab/src/db.rs @@ -509,8 +509,7 @@ pub type NotificationBatch = Vec<(UserId, proto::Notification)>; pub struct CreatedChannelMessage { pub message_id: MessageId, - pub participant_connection_ids: Vec, - pub channel_members: Vec, + pub participant_connection_ids: HashSet, pub notifications: NotificationBatch, } diff --git a/crates/collab/src/db/queries/buffers.rs b/crates/collab/src/db/queries/buffers.rs index bf4ac412fa..810d89bc14 100644 --- a/crates/collab/src/db/queries/buffers.rs +++ b/crates/collab/src/db/queries/buffers.rs @@ -440,12 +440,7 @@ impl Database { channel_id: ChannelId, user: UserId, operations: &[proto::Operation], - ) -> Result<( - Vec, - Vec, - i32, - Vec, - )> { + ) -> Result<(HashSet, i32, Vec)> { self.transaction(move |tx| async move { let channel = self.get_channel_internal(channel_id, &tx).await?; @@ -479,7 +474,6 @@ impl Database { .filter_map(|op| operation_to_storage(op, &buffer, serialization_version)) .collect::>(); - let mut channel_members; let max_version; if !operations.is_empty() { @@ -504,12 +498,6 @@ impl Database { ) .await?; - channel_members = self.get_channel_participants(&channel, &tx).await?; - let collaborators = self - .get_channel_buffer_collaborators_internal(channel_id, &tx) - .await?; - channel_members.retain(|member| !collaborators.contains(member)); - buffer_operation::Entity::insert_many(operations) .on_conflict( OnConflict::columns([ @@ -524,11 +512,10 @@ impl Database { .exec(&*tx) .await?; } else { - channel_members = Vec::new(); max_version = Vec::new(); } - let mut connections = Vec::new(); + let mut connections = HashSet::default(); let mut rows = channel_buffer_collaborator::Entity::find() .filter( Condition::all() @@ -538,13 +525,13 @@ impl Database { .await?; while let Some(row) = rows.next().await { let row = row?; - connections.push(ConnectionId { + connections.insert(ConnectionId { id: row.connection_id as u32, owner_id: row.connection_server_id.0 as u32, }); } - Ok((connections, channel_members, buffer.epoch, max_version)) + Ok((connections, buffer.epoch, max_version)) }) .await } diff --git a/crates/collab/src/db/queries/channels.rs b/crates/collab/src/db/queries/channels.rs index 3f168e0854..502fcd59c5 100644 --- a/crates/collab/src/db/queries/channels.rs +++ b/crates/collab/src/db/queries/channels.rs @@ -3,7 +3,7 @@ use rpc::{ proto::{channel_member::Kind, ChannelBufferVersion, VectorClockEntry}, ErrorCode, ErrorCodeExt, }; -use sea_orm::TryGetableMany; +use sea_orm::{DbBackend, TryGetableMany}; impl Database { #[cfg(test)] @@ -700,77 +700,73 @@ impl Database { pub async fn get_channel_participant_details( &self, channel_id: ChannelId, + filter: &str, + limit: u64, user_id: UserId, - ) -> Result> { - let (role, members) = self + ) -> Result<(Vec, Vec)> { + let members = self .transaction(move |tx| async move { let channel = self.get_channel_internal(channel_id, &tx).await?; - let role = self - .check_user_is_channel_participant(&channel, user_id, &tx) + self.check_user_is_channel_participant(&channel, user_id, &tx) .await?; - Ok(( - role, - self.get_channel_participant_details_internal(&channel, &tx) - .await?, - )) + let mut query = channel_member::Entity::find() + .find_also_related(user::Entity) + .filter(channel_member::Column::ChannelId.eq(channel.root_id())); + + if cfg!(any(test, sqlite)) && self.pool.get_database_backend() == DbBackend::Sqlite { + query = query.filter(Expr::cust_with_values( + "UPPER(github_login) LIKE ?", + [Self::fuzzy_like_string(&filter.to_uppercase())], + )) + } else { + query = query.filter(Expr::cust_with_values( + "github_login ILIKE $1", + [Self::fuzzy_like_string(filter)], + )) + } + let members = query.order_by( + Expr::cust( + "not role = 'admin', not role = 'member', not role = 'guest', not accepted, github_login", + ), + sea_orm::Order::Asc, + ) + .limit(limit) + .all(&*tx) + .await?; + + Ok(members) }) .await?; - if role == ChannelRole::Admin { - Ok(members - .into_iter() - .map(|channel_member| proto::ChannelMember { - role: channel_member.role.into(), - user_id: channel_member.user_id.to_proto(), - kind: if channel_member.accepted { + let mut users: Vec = Vec::with_capacity(members.len()); + + let members = members + .into_iter() + .map(|(member, user)| { + if let Some(user) = user { + users.push(proto::User { + id: user.id.to_proto(), + avatar_url: format!( + "https://github.com/{}.png?size=128", + user.github_login + ), + github_login: user.github_login, + }) + } + proto::ChannelMember { + role: member.role.into(), + user_id: member.user_id.to_proto(), + kind: if member.accepted { Kind::Member } else { Kind::Invitee } .into(), - }) - .collect()) - } else { - return Ok(members - .into_iter() - .filter_map(|member| { - if !member.accepted { - return None; - } - Some(proto::ChannelMember { - role: member.role.into(), - user_id: member.user_id.to_proto(), - kind: Kind::Member.into(), - }) - }) - .collect()); - } - } + } + }) + .collect(); - async fn get_channel_participant_details_internal( - &self, - channel: &channel::Model, - tx: &DatabaseTransaction, - ) -> Result> { - Ok(channel_member::Entity::find() - .filter(channel_member::Column::ChannelId.eq(channel.root_id())) - .all(tx) - .await?) - } - - /// Returns the participants in the given channel. - pub async fn get_channel_participants( - &self, - channel: &channel::Model, - tx: &DatabaseTransaction, - ) -> Result> { - let participants = self - .get_channel_participant_details_internal(channel, tx) - .await?; - Ok(participants - .into_iter() - .map(|member| member.user_id) - .collect()) + Ok((members, users)) } /// Returns whether the given user is an admin in the specified channel. diff --git a/crates/collab/src/db/queries/messages.rs b/crates/collab/src/db/queries/messages.rs index 2fa6edeb0a..bc82120a42 100644 --- a/crates/collab/src/db/queries/messages.rs +++ b/crates/collab/src/db/queries/messages.rs @@ -251,7 +251,7 @@ impl Database { .await?; let mut is_participant = false; - let mut participant_connection_ids = Vec::new(); + let mut participant_connection_ids = HashSet::default(); let mut participant_user_ids = Vec::new(); while let Some(row) = rows.next().await { let row = row?; @@ -259,7 +259,7 @@ impl Database { is_participant = true; } participant_user_ids.push(row.user_id); - participant_connection_ids.push(row.connection()); + participant_connection_ids.insert(row.connection()); } drop(rows); @@ -336,13 +336,9 @@ impl Database { } } - let mut channel_members = self.get_channel_participants(&channel, &tx).await?; - channel_members.retain(|member| !participant_user_ids.contains(member)); - Ok(CreatedChannelMessage { message_id, participant_connection_ids, - channel_members, notifications, }) }) diff --git a/crates/collab/src/db/tests/channel_tests.rs b/crates/collab/src/db/tests/channel_tests.rs index ad8f3467ec..4482549e91 100644 --- a/crates/collab/src/db/tests/channel_tests.rs +++ b/crates/collab/src/db/tests/channel_tests.rs @@ -1,7 +1,7 @@ use crate::{ db::{ tests::{channel_tree, new_test_connection, new_test_user}, - Channel, ChannelId, ChannelRole, Database, NewUserParams, RoomId, + Channel, ChannelId, ChannelRole, Database, NewUserParams, RoomId, UserId, }, test_both_dbs, }; @@ -40,15 +40,15 @@ async fn test_channels(db: &Arc) { .await .unwrap(); - let mut members = db - .transaction(|tx| async move { - let channel = db.get_channel_internal(replace_id, &tx).await?; - db.get_channel_participants(&channel, &tx).await - }) + let (members, _) = db + .get_channel_participant_details(replace_id, "", 10, a_id) .await .unwrap(); - members.sort(); - assert_eq!(members, &[a_id, b_id]); + let ids = members + .into_iter() + .map(|m| UserId::from_proto(m.user_id)) + .collect::>(); + assert_eq!(ids, &[a_id, b_id]); let rust_id = db.create_root_channel("rust", a_id).await.unwrap(); let cargo_id = db.create_sub_channel("cargo", rust_id, a_id).await.unwrap(); @@ -195,8 +195,8 @@ async fn test_channel_invites(db: &Arc) { assert_eq!(user_3_invites, &[channel_1_1]); - let mut members = db - .get_channel_participant_details(channel_1_1, user_1) + let (mut members, _) = db + .get_channel_participant_details(channel_1_1, "", 100, user_1) .await .unwrap(); @@ -231,8 +231,8 @@ async fn test_channel_invites(db: &Arc) { .await .unwrap(); - let members = db - .get_channel_participant_details(channel_1_3, user_1) + let (members, _) = db + .get_channel_participant_details(channel_1_3, "", 100, user_1) .await .unwrap(); assert_eq!( @@ -243,16 +243,16 @@ async fn test_channel_invites(db: &Arc) { kind: proto::channel_member::Kind::Member.into(), role: proto::ChannelRole::Admin.into(), }, - proto::ChannelMember { - user_id: user_2.to_proto(), - kind: proto::channel_member::Kind::Member.into(), - role: proto::ChannelRole::Member.into(), - }, proto::ChannelMember { user_id: user_3.to_proto(), kind: proto::channel_member::Kind::Invitee.into(), role: proto::ChannelRole::Admin.into(), }, + proto::ChannelMember { + user_id: user_2.to_proto(), + kind: proto::channel_member::Kind::Member.into(), + role: proto::ChannelRole::Member.into(), + }, ] ); } @@ -482,8 +482,8 @@ async fn test_user_is_channel_participant(db: &Arc) { .await .unwrap(); - let mut members = db - .get_channel_participant_details(public_channel_id, admin) + let (mut members, _) = db + .get_channel_participant_details(public_channel_id, "", 100, admin) .await .unwrap(); @@ -557,8 +557,8 @@ async fn test_user_is_channel_participant(db: &Arc) { .await .is_err()); - let mut members = db - .get_channel_participant_details(public_channel_id, admin) + let (mut members, _) = db + .get_channel_participant_details(public_channel_id, "", 100, admin) .await .unwrap(); @@ -594,8 +594,8 @@ async fn test_user_is_channel_participant(db: &Arc) { .unwrap(); // currently people invited to parent channels are not shown here - let mut members = db - .get_channel_participant_details(public_channel_id, admin) + let (mut members, _) = db + .get_channel_participant_details(public_channel_id, "", 100, admin) .await .unwrap(); @@ -663,8 +663,8 @@ async fn test_user_is_channel_participant(db: &Arc) { .await .unwrap(); - let mut members = db - .get_channel_participant_details(public_channel_id, admin) + let (mut members, _) = db + .get_channel_participant_details(public_channel_id, "", 100, admin) .await .unwrap(); diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 29f9644028..fdb14e03f5 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -3683,10 +3683,15 @@ async fn get_channel_members( ) -> Result<()> { let db = session.db().await; let channel_id = ChannelId::from_proto(request.channel_id); - let members = db - .get_channel_participant_details(channel_id, session.user_id()) + let limit = if request.limit == 0 { + u16::MAX as u64 + } else { + request.limit + }; + let (members, users) = db + .get_channel_participant_details(channel_id, &request.query, limit, session.user_id()) .await?; - response.send(proto::GetChannelMembersResponse { members })?; + response.send(proto::GetChannelMembersResponse { members, users })?; Ok(()) } @@ -3886,13 +3891,13 @@ async fn update_channel_buffer( let db = session.db().await; let channel_id = ChannelId::from_proto(request.channel_id); - let (collaborators, non_collaborators, epoch, version) = db + let (collaborators, epoch, version) = db .update_channel_buffer(channel_id, session.user_id(), &request.operations) .await?; channel_buffer_updated( session.connection_id, - collaborators, + collaborators.clone(), &proto::UpdateChannelBuffer { channel_id: channel_id.to_proto(), operations: request.operations, @@ -3902,25 +3907,29 @@ async fn update_channel_buffer( let pool = &*session.connection_pool().await; - broadcast( - None, - non_collaborators - .iter() - .flat_map(|user_id| pool.user_connection_ids(*user_id)), - |peer_id| { - session.peer.send( - peer_id, - proto::UpdateChannels { - latest_channel_buffer_versions: vec![proto::ChannelBufferVersion { - channel_id: channel_id.to_proto(), - epoch: epoch as u64, - version: version.clone(), - }], - ..Default::default() - }, - ) - }, - ); + let non_collaborators = + pool.channel_connection_ids(channel_id) + .filter_map(|(connection_id, _)| { + if collaborators.contains(&connection_id) { + None + } else { + Some(connection_id) + } + }); + + broadcast(None, non_collaborators, |peer_id| { + session.peer.send( + peer_id, + proto::UpdateChannels { + latest_channel_buffer_versions: vec![proto::ChannelBufferVersion { + channel_id: channel_id.to_proto(), + epoch: epoch as u64, + version: version.clone(), + }], + ..Default::default() + }, + ) + }); Ok(()) } @@ -4048,7 +4057,6 @@ async fn send_channel_message( let CreatedChannelMessage { message_id, participant_connection_ids, - channel_members, notifications, } = session .db() @@ -4079,7 +4087,7 @@ async fn send_channel_message( }; broadcast( Some(session.connection_id), - participant_connection_ids, + participant_connection_ids.clone(), |connection| { session.peer.send( connection, @@ -4095,24 +4103,27 @@ async fn send_channel_message( })?; let pool = &*session.connection_pool().await; - broadcast( - None, - channel_members - .iter() - .flat_map(|user_id| pool.user_connection_ids(*user_id)), - |peer_id| { - session.peer.send( - peer_id, - proto::UpdateChannels { - latest_channel_message_ids: vec![proto::ChannelMessageId { - channel_id: channel_id.to_proto(), - message_id: message_id.to_proto(), - }], - ..Default::default() - }, - ) - }, - ); + let non_participants = + pool.channel_connection_ids(channel_id) + .filter_map(|(connection_id, _)| { + if participant_connection_ids.contains(&connection_id) { + None + } else { + Some(connection_id) + } + }); + broadcast(None, non_participants, |peer_id| { + session.peer.send( + peer_id, + proto::UpdateChannels { + latest_channel_message_ids: vec![proto::ChannelMessageId { + channel_id: channel_id.to_proto(), + message_id: message_id.to_proto(), + }], + ..Default::default() + }, + ) + }); send_notifications(pool, &session.peer, notifications); Ok(()) diff --git a/crates/collab/src/tests/channel_tests.rs b/crates/collab/src/tests/channel_tests.rs index ae0035f3a2..d9fdab71e8 100644 --- a/crates/collab/src/tests/channel_tests.rs +++ b/crates/collab/src/tests/channel_tests.rs @@ -99,7 +99,7 @@ async fn test_core_channels( .channel_store() .update(cx_a, |store, cx| { assert!(!store.has_pending_channel_invite(channel_a_id, client_b.user_id().unwrap())); - store.get_channel_member_details(channel_a_id, cx) + store.fuzzy_search_members(channel_a_id, "".to_string(), 10, cx) }) .await .unwrap(); diff --git a/crates/collab_ui/src/collab_panel.rs b/crates/collab_ui/src/collab_panel.rs index 5b3687f0db..9f0144529f 100644 --- a/crates/collab_ui/src/collab_panel.rs +++ b/crates/collab_ui/src/collab_panel.rs @@ -1569,11 +1569,28 @@ impl CollabPanel { *pending_name = Some(channel_name.clone()); - self.channel_store - .update(cx, |channel_store, cx| { - channel_store.create_channel(&channel_name, *location, cx) + let create = self.channel_store.update(cx, |channel_store, cx| { + channel_store.create_channel(&channel_name, *location, cx) + }); + if location.is_none() { + cx.spawn(|this, mut cx| async move { + let channel_id = create.await?; + this.update(&mut cx, |this, cx| { + this.show_channel_modal( + channel_id, + channel_modal::Mode::InviteMembers, + cx, + ) + }) }) - .detach(); + .detach_and_prompt_err( + "Failed to create channel", + cx, + |_, _| None, + ); + } else { + create.detach_and_prompt_err("Failed to create channel", cx, |_, _| None); + } cx.notify(); } ChannelEditingState::Rename { @@ -1859,12 +1876,8 @@ impl CollabPanel { 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| { ChannelModal::new( @@ -1872,7 +1885,6 @@ impl CollabPanel { channel_store.clone(), channel_id, mode, - members, cx, ) }); diff --git a/crates/collab_ui/src/collab_panel/channel_modal.rs b/crates/collab_ui/src/collab_panel/channel_modal.rs index 3bc3fc1650..4e943d31f7 100644 --- a/crates/collab_ui/src/collab_panel/channel_modal.rs +++ b/crates/collab_ui/src/collab_panel/channel_modal.rs @@ -37,7 +37,6 @@ impl ChannelModal { channel_store: Model, channel_id: ChannelId, mode: Mode, - members: Vec, cx: &mut ViewContext, ) -> Self { cx.observe(&channel_store, |_, _, cx| cx.notify()).detach(); @@ -54,7 +53,8 @@ impl ChannelModal { channel_id, match_candidates: Vec::new(), context_menu: None, - members, + members: Vec::new(), + has_all_members: false, mode, }, cx, @@ -78,37 +78,15 @@ impl ChannelModal { } fn set_mode(&mut self, mode: Mode, cx: &mut ViewContext) { - let channel_store = self.channel_store.clone(); - let channel_id = self.channel_id; - cx.spawn(|this, mut cx| async move { - if mode == Mode::ManageMembers { - let mut members = channel_store - .update(&mut cx, |channel_store, cx| { - channel_store.get_channel_member_details(channel_id, cx) - })? - .await?; - - members.sort_by(|a, b| a.sort_key().cmp(&b.sort_key())); - - this.update(&mut cx, |this, cx| { - this.picker - .update(cx, |picker, _| picker.delegate.members = members); - })?; - } - - this.update(&mut cx, |this, cx| { - this.picker.update(cx, |picker, cx| { - let delegate = &mut picker.delegate; - delegate.mode = mode; - delegate.selected_index = 0; - picker.set_query("", cx); - picker.update_matches(picker.query(cx), cx); - cx.notify() - }); - cx.notify() - }) - }) - .detach(); + self.picker.update(cx, |picker, cx| { + let delegate = &mut picker.delegate; + delegate.mode = mode; + delegate.selected_index = 0; + picker.set_query("", cx); + picker.update_matches(picker.query(cx), cx); + cx.notify() + }); + cx.notify() } fn set_channel_visibility(&mut self, selection: &Selection, cx: &mut ViewContext) { @@ -260,6 +238,7 @@ pub struct ChannelModalDelegate { mode: Mode, match_candidates: Vec, members: Vec, + has_all_members: bool, context_menu: Option<(View, Subscription)>, } @@ -288,37 +267,59 @@ impl PickerDelegate for ChannelModalDelegate { fn update_matches(&mut self, query: String, cx: &mut ViewContext>) -> Task<()> { match self.mode { Mode::ManageMembers => { - self.match_candidates.clear(); - self.match_candidates - .extend(self.members.iter().enumerate().map(|(id, member)| { - StringMatchCandidate { - id, - string: member.user.github_login.clone(), - char_bag: member.user.github_login.chars().collect(), + if self.has_all_members { + self.match_candidates.clear(); + self.match_candidates + .extend(self.members.iter().enumerate().map(|(id, member)| { + StringMatchCandidate { + id, + string: member.user.github_login.clone(), + char_bag: member.user.github_login.chars().collect(), + } + })); + + let matches = cx.background_executor().block(match_strings( + &self.match_candidates, + &query, + true, + usize::MAX, + &Default::default(), + cx.background_executor().clone(), + )); + + cx.spawn(|picker, mut cx| async move { + picker + .update(&mut cx, |picker, cx| { + let delegate = &mut picker.delegate; + delegate.matching_member_indices.clear(); + delegate + .matching_member_indices + .extend(matches.into_iter().map(|m| m.candidate_id)); + cx.notify(); + }) + .ok(); + }) + } else { + let search_members = self.channel_store.update(cx, |store, cx| { + store.fuzzy_search_members(self.channel_id, query.clone(), 100, cx) + }); + cx.spawn(|picker, mut cx| async move { + async { + let members = search_members.await?; + picker.update(&mut cx, |picker, cx| { + picker.delegate.has_all_members = + query == "" && members.len() < 100; + picker.delegate.matching_member_indices = + (0..members.len()).collect(); + picker.delegate.members = members; + cx.notify(); + })?; + anyhow::Ok(()) } - })); - - let matches = cx.background_executor().block(match_strings( - &self.match_candidates, - &query, - true, - usize::MAX, - &Default::default(), - cx.background_executor().clone(), - )); - - cx.spawn(|picker, mut cx| async move { - picker - .update(&mut cx, |picker, cx| { - let delegate = &mut picker.delegate; - delegate.matching_member_indices.clear(); - delegate - .matching_member_indices - .extend(matches.into_iter().map(|m| m.candidate_id)); - cx.notify(); - }) - .ok(); - }) + .log_err() + .await; + }) + } } Mode::InviteMembers => { let search_users = self diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 4a483d2f67..d6c0758cf2 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -1268,10 +1268,13 @@ message DeleteChannel { message GetChannelMembers { uint64 channel_id = 1; + string query = 2; + uint64 limit = 3; } message GetChannelMembersResponse { repeated ChannelMember members = 1; + repeated User users = 2; } message ChannelMember {