Assign unique color indices to room participants, use those instead of replica_ids
Co-authored-by: Conrad <conrad@zed.dev> Co-authored-by: Antonio <antonio@zed.dev>
This commit is contained in:
parent
7711530704
commit
545b5e0161
35 changed files with 707 additions and 639 deletions
|
@ -510,7 +510,7 @@ pub struct RefreshedRoom {
|
|||
|
||||
pub struct RefreshedChannelBuffer {
|
||||
pub connection_ids: Vec<ConnectionId>,
|
||||
pub removed_collaborators: Vec<proto::RemoveChannelBufferCollaborator>,
|
||||
pub collaborators: Vec<proto::Collaborator>,
|
||||
}
|
||||
|
||||
pub struct Project {
|
||||
|
|
|
@ -2,6 +2,12 @@ use super::*;
|
|||
use prost::Message;
|
||||
use text::{EditOperation, UndoOperation};
|
||||
|
||||
pub struct LeftChannelBuffer {
|
||||
pub channel_id: ChannelId,
|
||||
pub collaborators: Vec<proto::Collaborator>,
|
||||
pub connections: Vec<ConnectionId>,
|
||||
}
|
||||
|
||||
impl Database {
|
||||
pub async fn join_channel_buffer(
|
||||
&self,
|
||||
|
@ -204,23 +210,26 @@ impl Database {
|
|||
server_id: ServerId,
|
||||
) -> Result<RefreshedChannelBuffer> {
|
||||
self.transaction(|tx| async move {
|
||||
let collaborators = channel_buffer_collaborator::Entity::find()
|
||||
let db_collaborators = channel_buffer_collaborator::Entity::find()
|
||||
.filter(channel_buffer_collaborator::Column::ChannelId.eq(channel_id))
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
|
||||
let mut connection_ids = Vec::new();
|
||||
let mut removed_collaborators = Vec::new();
|
||||
let mut collaborators = Vec::new();
|
||||
let mut collaborator_ids_to_remove = Vec::new();
|
||||
for collaborator in &collaborators {
|
||||
if !collaborator.connection_lost && collaborator.connection_server_id == server_id {
|
||||
connection_ids.push(collaborator.connection());
|
||||
for db_collaborator in &db_collaborators {
|
||||
if !db_collaborator.connection_lost
|
||||
&& db_collaborator.connection_server_id == server_id
|
||||
{
|
||||
connection_ids.push(db_collaborator.connection());
|
||||
collaborators.push(proto::Collaborator {
|
||||
peer_id: Some(db_collaborator.connection().into()),
|
||||
replica_id: db_collaborator.replica_id.0 as u32,
|
||||
user_id: db_collaborator.user_id.to_proto(),
|
||||
})
|
||||
} else {
|
||||
removed_collaborators.push(proto::RemoveChannelBufferCollaborator {
|
||||
channel_id: channel_id.to_proto(),
|
||||
peer_id: Some(collaborator.connection().into()),
|
||||
});
|
||||
collaborator_ids_to_remove.push(collaborator.id);
|
||||
collaborator_ids_to_remove.push(db_collaborator.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -231,7 +240,7 @@ impl Database {
|
|||
|
||||
Ok(RefreshedChannelBuffer {
|
||||
connection_ids,
|
||||
removed_collaborators,
|
||||
collaborators,
|
||||
})
|
||||
})
|
||||
.await
|
||||
|
@ -241,7 +250,7 @@ impl Database {
|
|||
&self,
|
||||
channel_id: ChannelId,
|
||||
connection: ConnectionId,
|
||||
) -> Result<Vec<ConnectionId>> {
|
||||
) -> Result<LeftChannelBuffer> {
|
||||
self.transaction(|tx| async move {
|
||||
self.leave_channel_buffer_internal(channel_id, connection, &*tx)
|
||||
.await
|
||||
|
@ -275,7 +284,7 @@ impl Database {
|
|||
pub async fn leave_channel_buffers(
|
||||
&self,
|
||||
connection: ConnectionId,
|
||||
) -> Result<Vec<(ChannelId, Vec<ConnectionId>)>> {
|
||||
) -> Result<Vec<LeftChannelBuffer>> {
|
||||
self.transaction(|tx| async move {
|
||||
#[derive(Debug, Clone, Copy, EnumIter, DeriveColumn)]
|
||||
enum QueryChannelIds {
|
||||
|
@ -294,10 +303,10 @@ impl Database {
|
|||
|
||||
let mut result = Vec::new();
|
||||
for channel_id in channel_ids {
|
||||
let collaborators = self
|
||||
let left_channel_buffer = self
|
||||
.leave_channel_buffer_internal(channel_id, connection, &*tx)
|
||||
.await?;
|
||||
result.push((channel_id, collaborators));
|
||||
result.push(left_channel_buffer);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
|
@ -310,7 +319,7 @@ impl Database {
|
|||
channel_id: ChannelId,
|
||||
connection: ConnectionId,
|
||||
tx: &DatabaseTransaction,
|
||||
) -> Result<Vec<ConnectionId>> {
|
||||
) -> Result<LeftChannelBuffer> {
|
||||
let result = channel_buffer_collaborator::Entity::delete_many()
|
||||
.filter(
|
||||
Condition::all()
|
||||
|
@ -327,6 +336,7 @@ impl Database {
|
|||
Err(anyhow!("not a collaborator on this project"))?;
|
||||
}
|
||||
|
||||
let mut collaborators = Vec::new();
|
||||
let mut connections = Vec::new();
|
||||
let mut rows = channel_buffer_collaborator::Entity::find()
|
||||
.filter(
|
||||
|
@ -336,19 +346,26 @@ impl Database {
|
|||
.await?;
|
||||
while let Some(row) = rows.next().await {
|
||||
let row = row?;
|
||||
connections.push(ConnectionId {
|
||||
id: row.connection_id as u32,
|
||||
owner_id: row.connection_server_id.0 as u32,
|
||||
let connection = row.connection();
|
||||
connections.push(connection);
|
||||
collaborators.push(proto::Collaborator {
|
||||
peer_id: Some(connection.into()),
|
||||
replica_id: row.replica_id.0 as u32,
|
||||
user_id: row.user_id.to_proto(),
|
||||
});
|
||||
}
|
||||
|
||||
drop(rows);
|
||||
|
||||
if connections.is_empty() {
|
||||
if collaborators.is_empty() {
|
||||
self.snapshot_channel_buffer(channel_id, &tx).await?;
|
||||
}
|
||||
|
||||
Ok(connections)
|
||||
Ok(LeftChannelBuffer {
|
||||
channel_id,
|
||||
collaborators,
|
||||
connections,
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_channel_buffer_collaborators(
|
||||
|
|
|
@ -152,6 +152,7 @@ impl Database {
|
|||
room_id: ActiveValue::set(room_id),
|
||||
user_id: ActiveValue::set(called_user_id),
|
||||
answering_connection_lost: ActiveValue::set(false),
|
||||
color_index: ActiveValue::NotSet,
|
||||
calling_user_id: ActiveValue::set(calling_user_id),
|
||||
calling_connection_id: ActiveValue::set(calling_connection.id as i32),
|
||||
calling_connection_server_id: ActiveValue::set(Some(ServerId(
|
||||
|
@ -283,6 +284,22 @@ impl Database {
|
|||
.await?
|
||||
.ok_or_else(|| anyhow!("no such room"))?;
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)]
|
||||
enum QueryColorIndices {
|
||||
ColorIndex,
|
||||
}
|
||||
let existing_color_indices: Vec<i32> = room_participant::Entity::find()
|
||||
.filter(room_participant::Column::RoomId.eq(room_id))
|
||||
.select_only()
|
||||
.column(room_participant::Column::ColorIndex)
|
||||
.into_values::<_, QueryColorIndices>()
|
||||
.all(&*tx)
|
||||
.await?;
|
||||
let mut color_index = 0;
|
||||
while existing_color_indices.contains(&color_index) {
|
||||
color_index += 1;
|
||||
}
|
||||
|
||||
if let Some(channel_id) = channel_id {
|
||||
self.check_user_is_channel_member(channel_id, user_id, &*tx)
|
||||
.await?;
|
||||
|
@ -300,6 +317,7 @@ impl Database {
|
|||
calling_connection_server_id: ActiveValue::set(Some(ServerId(
|
||||
connection.owner_id as i32,
|
||||
))),
|
||||
color_index: ActiveValue::Set(color_index),
|
||||
..Default::default()
|
||||
}])
|
||||
.on_conflict(
|
||||
|
@ -322,6 +340,7 @@ impl Database {
|
|||
.add(room_participant::Column::AnsweringConnectionId.is_null()),
|
||||
)
|
||||
.set(room_participant::ActiveModel {
|
||||
color_index: ActiveValue::Set(color_index),
|
||||
answering_connection_id: ActiveValue::set(Some(connection.id as i32)),
|
||||
answering_connection_server_id: ActiveValue::set(Some(ServerId(
|
||||
connection.owner_id as i32,
|
||||
|
@ -1071,6 +1090,7 @@ impl Database {
|
|||
peer_id: Some(answering_connection.into()),
|
||||
projects: Default::default(),
|
||||
location: Some(proto::ParticipantLocation { variant: location }),
|
||||
color_index: db_participant.color_index as u32,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
|
|
|
@ -18,6 +18,7 @@ pub struct Model {
|
|||
pub calling_user_id: UserId,
|
||||
pub calling_connection_id: i32,
|
||||
pub calling_connection_server_id: Option<ServerId>,
|
||||
pub color_index: i32,
|
||||
}
|
||||
|
||||
impl Model {
|
||||
|
|
|
@ -134,12 +134,12 @@ async fn test_channel_buffers(db: &Arc<Database>) {
|
|||
let zed_collaborats = db.get_channel_buffer_collaborators(zed_id).await.unwrap();
|
||||
assert_eq!(zed_collaborats, &[a_id, b_id]);
|
||||
|
||||
let collaborators = db
|
||||
let left_buffer = db
|
||||
.leave_channel_buffer(zed_id, connection_id_b)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(collaborators, &[connection_id_a],);
|
||||
assert_eq!(left_buffer.connections, &[connection_id_a],);
|
||||
|
||||
let cargo_id = db.create_root_channel("cargo", "2", a_id).await.unwrap();
|
||||
let _ = db
|
||||
|
|
|
@ -38,8 +38,8 @@ use lazy_static::lazy_static;
|
|||
use prometheus::{register_int_gauge, IntGauge};
|
||||
use rpc::{
|
||||
proto::{
|
||||
self, Ack, AddChannelBufferCollaborator, AnyTypedEnvelope, ChannelEdge, EntityMessage,
|
||||
EnvelopedMessage, LiveKitConnectionInfo, RequestMessage,
|
||||
self, Ack, AnyTypedEnvelope, ChannelEdge, EntityMessage, EnvelopedMessage,
|
||||
LiveKitConnectionInfo, RequestMessage, UpdateChannelBufferCollaborators,
|
||||
},
|
||||
Connection, ConnectionId, Peer, Receipt, TypedEnvelope,
|
||||
};
|
||||
|
@ -313,9 +313,16 @@ impl Server {
|
|||
.trace_err()
|
||||
{
|
||||
for connection_id in refreshed_channel_buffer.connection_ids {
|
||||
for message in &refreshed_channel_buffer.removed_collaborators {
|
||||
peer.send(connection_id, message.clone()).trace_err();
|
||||
}
|
||||
peer.send(
|
||||
connection_id,
|
||||
proto::UpdateChannelBufferCollaborators {
|
||||
channel_id: channel_id.to_proto(),
|
||||
collaborators: refreshed_channel_buffer
|
||||
.collaborators
|
||||
.clone(),
|
||||
},
|
||||
)
|
||||
.trace_err();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2654,18 +2661,12 @@ async fn join_channel_buffer(
|
|||
.join_channel_buffer(channel_id, session.user_id, session.connection_id)
|
||||
.await?;
|
||||
|
||||
let replica_id = open_response.replica_id;
|
||||
let collaborators = open_response.collaborators.clone();
|
||||
|
||||
response.send(open_response)?;
|
||||
|
||||
let update = AddChannelBufferCollaborator {
|
||||
let update = UpdateChannelBufferCollaborators {
|
||||
channel_id: channel_id.to_proto(),
|
||||
collaborator: Some(proto::Collaborator {
|
||||
user_id: session.user_id.to_proto(),
|
||||
peer_id: Some(session.connection_id.into()),
|
||||
replica_id,
|
||||
}),
|
||||
collaborators: collaborators.clone(),
|
||||
};
|
||||
channel_buffer_updated(
|
||||
session.connection_id,
|
||||
|
@ -2712,8 +2713,8 @@ async fn rejoin_channel_buffers(
|
|||
.rejoin_channel_buffers(&request.buffers, session.user_id, session.connection_id)
|
||||
.await?;
|
||||
|
||||
for buffer in &buffers {
|
||||
let collaborators_to_notify = buffer
|
||||
for rejoined_buffer in &buffers {
|
||||
let collaborators_to_notify = rejoined_buffer
|
||||
.buffer
|
||||
.collaborators
|
||||
.iter()
|
||||
|
@ -2721,10 +2722,9 @@ async fn rejoin_channel_buffers(
|
|||
channel_buffer_updated(
|
||||
session.connection_id,
|
||||
collaborators_to_notify,
|
||||
&proto::UpdateChannelBufferCollaborator {
|
||||
channel_id: buffer.buffer.channel_id,
|
||||
old_peer_id: Some(buffer.old_connection_id.into()),
|
||||
new_peer_id: Some(session.connection_id.into()),
|
||||
&proto::UpdateChannelBufferCollaborators {
|
||||
channel_id: rejoined_buffer.buffer.channel_id,
|
||||
collaborators: rejoined_buffer.buffer.collaborators.clone(),
|
||||
},
|
||||
&session.peer,
|
||||
);
|
||||
|
@ -2745,7 +2745,7 @@ async fn leave_channel_buffer(
|
|||
let db = session.db().await;
|
||||
let channel_id = ChannelId::from_proto(request.channel_id);
|
||||
|
||||
let collaborators_to_notify = db
|
||||
let left_buffer = db
|
||||
.leave_channel_buffer(channel_id, session.connection_id)
|
||||
.await?;
|
||||
|
||||
|
@ -2753,10 +2753,10 @@ async fn leave_channel_buffer(
|
|||
|
||||
channel_buffer_updated(
|
||||
session.connection_id,
|
||||
collaborators_to_notify,
|
||||
&proto::RemoveChannelBufferCollaborator {
|
||||
left_buffer.connections,
|
||||
&proto::UpdateChannelBufferCollaborators {
|
||||
channel_id: channel_id.to_proto(),
|
||||
peer_id: Some(session.connection_id.into()),
|
||||
collaborators: left_buffer.collaborators,
|
||||
},
|
||||
&session.peer,
|
||||
);
|
||||
|
@ -3231,13 +3231,13 @@ async fn leave_channel_buffers_for_session(session: &Session) -> Result<()> {
|
|||
.leave_channel_buffers(session.connection_id)
|
||||
.await?;
|
||||
|
||||
for (channel_id, connections) in left_channel_buffers {
|
||||
for left_buffer in left_channel_buffers {
|
||||
channel_buffer_updated(
|
||||
session.connection_id,
|
||||
connections,
|
||||
&proto::RemoveChannelBufferCollaborator {
|
||||
channel_id: channel_id.to_proto(),
|
||||
peer_id: Some(session.connection_id.into()),
|
||||
left_buffer.connections,
|
||||
&proto::UpdateChannelBufferCollaborators {
|
||||
channel_id: left_buffer.channel_id.to_proto(),
|
||||
collaborators: left_buffer.collaborators,
|
||||
},
|
||||
&session.peer,
|
||||
);
|
||||
|
|
|
@ -4,14 +4,16 @@ use crate::{
|
|||
};
|
||||
use call::ActiveCall;
|
||||
use channel::Channel;
|
||||
use client::UserId;
|
||||
use client::{Collaborator, UserId};
|
||||
use collab_ui::channel_view::ChannelView;
|
||||
use collections::HashMap;
|
||||
use editor::{Anchor, Editor, ToOffset};
|
||||
use futures::future;
|
||||
use gpui::{executor::Deterministic, ModelHandle, TestAppContext};
|
||||
use rpc::{proto, RECEIVE_TIMEOUT};
|
||||
use gpui::{executor::Deterministic, ModelHandle, TestAppContext, ViewContext};
|
||||
use rpc::{proto::PeerId, RECEIVE_TIMEOUT};
|
||||
use serde_json::json;
|
||||
use std::sync::Arc;
|
||||
use std::{ops::Range, sync::Arc};
|
||||
use theme::ColorIndex;
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_core_channel_buffers(
|
||||
|
@ -120,10 +122,10 @@ async fn test_core_channel_buffers(
|
|||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_channel_buffer_replica_ids(
|
||||
async fn test_channel_notes_color_indices(
|
||||
deterministic: Arc<Deterministic>,
|
||||
cx_a: &mut TestAppContext,
|
||||
cx_b: &mut TestAppContext,
|
||||
mut cx_a: &mut TestAppContext,
|
||||
mut cx_b: &mut TestAppContext,
|
||||
cx_c: &mut TestAppContext,
|
||||
) {
|
||||
deterministic.forbid_parking();
|
||||
|
@ -132,6 +134,13 @@ async fn test_channel_buffer_replica_ids(
|
|||
let client_b = server.create_client(cx_b, "user_b").await;
|
||||
let client_c = server.create_client(cx_c, "user_c").await;
|
||||
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
let active_call_b = cx_b.read(ActiveCall::global);
|
||||
|
||||
cx_a.update(editor::init);
|
||||
cx_b.update(editor::init);
|
||||
cx_c.update(editor::init);
|
||||
|
||||
let channel_id = server
|
||||
.make_channel(
|
||||
"the-channel",
|
||||
|
@ -141,136 +150,158 @@ async fn test_channel_buffer_replica_ids(
|
|||
)
|
||||
.await;
|
||||
|
||||
let active_call_a = cx_a.read(ActiveCall::global);
|
||||
let active_call_b = cx_b.read(ActiveCall::global);
|
||||
let active_call_c = cx_c.read(ActiveCall::global);
|
||||
|
||||
// Clients A and B join a channel.
|
||||
active_call_a
|
||||
.update(cx_a, |call, cx| call.join_channel(channel_id, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
active_call_b
|
||||
.update(cx_b, |call, cx| call.join_channel(channel_id, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Clients A, B, and C join a channel buffer
|
||||
// C first so that the replica IDs in the project and the channel buffer are different
|
||||
let channel_buffer_c = client_c
|
||||
.channel_store()
|
||||
.update(cx_c, |store, cx| store.open_channel_buffer(channel_id, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let channel_buffer_b = client_b
|
||||
.channel_store()
|
||||
.update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let channel_buffer_a = client_a
|
||||
.channel_store()
|
||||
.update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Client B shares a project
|
||||
client_b
|
||||
client_a
|
||||
.fs()
|
||||
.insert_tree("/dir", json!({ "file.txt": "contents" }))
|
||||
.insert_tree("/root", json!({"file.txt": "123"}))
|
||||
.await;
|
||||
let (project_b, _) = client_b.build_local_project("/dir", cx_b).await;
|
||||
let shared_project_id = active_call_b
|
||||
.update(cx_b, |call, cx| call.share_project(project_b.clone(), cx))
|
||||
let (project_a, worktree_id_a) = client_a.build_local_project("/root", cx_a).await;
|
||||
let project_b = client_b.build_empty_local_project(cx_b);
|
||||
let project_c = client_c.build_empty_local_project(cx_c);
|
||||
let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
|
||||
let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
|
||||
let workspace_c = client_c.build_workspace(&project_c, cx_c).root(cx_c);
|
||||
|
||||
// Clients A, B, and C open the channel notes
|
||||
let channel_view_a = cx_a
|
||||
.update(|cx| ChannelView::open(channel_id, workspace_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let channel_view_b = cx_b
|
||||
.update(|cx| ChannelView::open(channel_id, workspace_b.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let channel_view_c = cx_c
|
||||
.update(|cx| ChannelView::open(channel_id, workspace_c.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Client A joins the project
|
||||
let project_a = client_a.build_remote_project(shared_project_id, cx_a).await;
|
||||
// Clients A, B, and C all insert and select some text
|
||||
channel_view_a.update(cx_a, |notes, cx| {
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
editor.insert("a", cx);
|
||||
editor.change_selections(None, cx, |selections| {
|
||||
selections.select_ranges(vec![0..1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
deterministic.run_until_parked();
|
||||
|
||||
// Client C is in a separate project.
|
||||
client_c.fs().insert_tree("/dir", json!({})).await;
|
||||
let (separate_project_c, _) = client_c.build_local_project("/dir", cx_c).await;
|
||||
|
||||
// Note that each user has a different replica id in the projects vs the
|
||||
// channel buffer.
|
||||
channel_buffer_a.read_with(cx_a, |channel_buffer, cx| {
|
||||
assert_eq!(project_a.read(cx).replica_id(), 1);
|
||||
assert_eq!(channel_buffer.buffer().read(cx).replica_id(), 2);
|
||||
channel_view_b.update(cx_b, |notes, cx| {
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
editor.move_down(&Default::default(), cx);
|
||||
editor.insert("b", cx);
|
||||
editor.change_selections(None, cx, |selections| {
|
||||
selections.select_ranges(vec![1..2]);
|
||||
});
|
||||
});
|
||||
});
|
||||
channel_buffer_b.read_with(cx_b, |channel_buffer, cx| {
|
||||
assert_eq!(project_b.read(cx).replica_id(), 0);
|
||||
assert_eq!(channel_buffer.buffer().read(cx).replica_id(), 1);
|
||||
});
|
||||
channel_buffer_c.read_with(cx_c, |channel_buffer, cx| {
|
||||
// C is not in the project
|
||||
assert_eq!(channel_buffer.buffer().read(cx).replica_id(), 0);
|
||||
deterministic.run_until_parked();
|
||||
channel_view_c.update(cx_c, |notes, cx| {
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
editor.move_down(&Default::default(), cx);
|
||||
editor.insert("c", cx);
|
||||
editor.change_selections(None, cx, |selections| {
|
||||
selections.select_ranges(vec![2..3]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
let channel_window_a =
|
||||
cx_a.add_window(|cx| ChannelView::new(project_a.clone(), channel_buffer_a.clone(), cx));
|
||||
let channel_window_b =
|
||||
cx_b.add_window(|cx| ChannelView::new(project_b.clone(), channel_buffer_b.clone(), cx));
|
||||
let channel_window_c = cx_c.add_window(|cx| {
|
||||
ChannelView::new(separate_project_c.clone(), channel_buffer_c.clone(), cx)
|
||||
// Client A sees clients B and C without assigned colors, because they aren't
|
||||
// in a call together.
|
||||
deterministic.run_until_parked();
|
||||
channel_view_a.update(cx_a, |notes, cx| {
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
assert_remote_selections(editor, &[(None, 1..2), (None, 2..3)], cx);
|
||||
});
|
||||
});
|
||||
|
||||
let channel_view_a = channel_window_a.root(cx_a);
|
||||
let channel_view_b = channel_window_b.root(cx_b);
|
||||
let channel_view_c = channel_window_c.root(cx_c);
|
||||
// Clients A and B join the same call.
|
||||
for (call, cx) in [(&active_call_a, &mut cx_a), (&active_call_b, &mut cx_b)] {
|
||||
call.update(*cx, |call, cx| call.join_channel(channel_id, cx))
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
// For clients A and B, the replica ids in the channel buffer are mapped
|
||||
// so that they match the same users' replica ids in their shared project.
|
||||
channel_view_a.read_with(cx_a, |view, cx| {
|
||||
assert_eq!(
|
||||
view.editor.read(cx).replica_id_map().unwrap(),
|
||||
&[(1, 0), (2, 1)].into_iter().collect::<HashMap<_, _>>()
|
||||
);
|
||||
// Clients A and B see each other with two different assigned colors. Client C
|
||||
// still doesn't have a color.
|
||||
deterministic.run_until_parked();
|
||||
channel_view_a.update(cx_a, |notes, cx| {
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
assert_remote_selections(editor, &[(Some(ColorIndex(1)), 1..2), (None, 2..3)], cx);
|
||||
});
|
||||
});
|
||||
channel_view_b.read_with(cx_b, |view, cx| {
|
||||
assert_eq!(
|
||||
view.editor.read(cx).replica_id_map().unwrap(),
|
||||
&[(1, 0), (2, 1)].into_iter().collect::<HashMap<u16, u16>>(),
|
||||
)
|
||||
channel_view_b.update(cx_b, |notes, cx| {
|
||||
notes.editor.update(cx, |editor, cx| {
|
||||
assert_remote_selections(editor, &[(Some(ColorIndex(0)), 0..1), (None, 2..3)], cx);
|
||||
});
|
||||
});
|
||||
|
||||
// Client C only sees themself, as they're not part of any shared project
|
||||
channel_view_c.read_with(cx_c, |view, cx| {
|
||||
assert_eq!(
|
||||
view.editor.read(cx).replica_id_map().unwrap(),
|
||||
&[(0, 0)].into_iter().collect::<HashMap<u16, u16>>(),
|
||||
);
|
||||
});
|
||||
|
||||
// Client C joins the project that clients A and B are in.
|
||||
active_call_c
|
||||
.update(cx_c, |call, cx| call.join_channel(channel_id, cx))
|
||||
// Client A shares a project, and client B joins.
|
||||
let project_id = active_call_a
|
||||
.update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
let project_c = client_c.build_remote_project(shared_project_id, cx_c).await;
|
||||
deterministic.run_until_parked();
|
||||
project_c.read_with(cx_c, |project, _| {
|
||||
assert_eq!(project.replica_id(), 2);
|
||||
});
|
||||
let project_b = client_b.build_remote_project(project_id, cx_b).await;
|
||||
let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
|
||||
|
||||
// For clients A and B, client C's replica id in the channel buffer is
|
||||
// now mapped to their replica id in the shared project.
|
||||
channel_view_a.read_with(cx_a, |view, cx| {
|
||||
assert_eq!(
|
||||
view.editor.read(cx).replica_id_map().unwrap(),
|
||||
&[(1, 0), (2, 1), (0, 2)]
|
||||
.into_iter()
|
||||
.collect::<HashMap<_, _>>()
|
||||
);
|
||||
// Clients A and B open the same file.
|
||||
let editor_a = workspace_a
|
||||
.update(cx_a, |workspace, cx| {
|
||||
workspace.open_path((worktree_id_a, "file.txt"), None, true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
let editor_b = workspace_b
|
||||
.update(cx_b, |workspace, cx| {
|
||||
workspace.open_path((worktree_id_a, "file.txt"), None, true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.downcast::<Editor>()
|
||||
.unwrap();
|
||||
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
editor.change_selections(None, cx, |selections| {
|
||||
selections.select_ranges(vec![0..1]);
|
||||
});
|
||||
});
|
||||
channel_view_b.read_with(cx_b, |view, cx| {
|
||||
assert_eq!(
|
||||
view.editor.read(cx).replica_id_map().unwrap(),
|
||||
&[(1, 0), (2, 1), (0, 2)]
|
||||
.into_iter()
|
||||
.collect::<HashMap<_, _>>(),
|
||||
)
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
editor.change_selections(None, cx, |selections| {
|
||||
selections.select_ranges(vec![2..3]);
|
||||
});
|
||||
});
|
||||
deterministic.run_until_parked();
|
||||
|
||||
// Clients A and B see each other with the same colors as in the channel notes.
|
||||
editor_a.update(cx_a, |editor, cx| {
|
||||
assert_remote_selections(editor, &[(Some(ColorIndex(1)), 2..3)], cx);
|
||||
});
|
||||
editor_b.update(cx_b, |editor, cx| {
|
||||
assert_remote_selections(editor, &[(Some(ColorIndex(0)), 0..1)], cx);
|
||||
});
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_remote_selections(
|
||||
editor: &mut Editor,
|
||||
expected_selections: &[(Option<ColorIndex>, Range<usize>)],
|
||||
cx: &mut ViewContext<Editor>,
|
||||
) {
|
||||
let snapshot = editor.snapshot(cx);
|
||||
let range = Anchor::min()..Anchor::max();
|
||||
let remote_selections = snapshot
|
||||
.remote_selections_in_range(&range, editor.collaboration_hub().unwrap(), cx)
|
||||
.map(|s| {
|
||||
let start = s.selection.start.to_offset(&snapshot.buffer_snapshot);
|
||||
let end = s.selection.end.to_offset(&snapshot.buffer_snapshot);
|
||||
(s.color_index, start..end)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
remote_selections, expected_selections,
|
||||
"incorrect remote selections"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
|
@ -568,13 +599,9 @@ async fn test_channel_buffers_and_server_restarts(
|
|||
|
||||
channel_buffer_a.read_with(cx_a, |buffer_a, _| {
|
||||
channel_buffer_b.read_with(cx_b, |buffer_b, _| {
|
||||
assert_eq!(
|
||||
buffer_a
|
||||
.collaborators()
|
||||
.iter()
|
||||
.map(|c| c.user_id)
|
||||
.collect::<Vec<_>>(),
|
||||
vec![client_a.user_id().unwrap(), client_b.user_id().unwrap()]
|
||||
assert_collaborators(
|
||||
buffer_a.collaborators(),
|
||||
&[client_a.user_id(), client_b.user_id()],
|
||||
);
|
||||
assert_eq!(buffer_a.collaborators(), buffer_b.collaborators());
|
||||
});
|
||||
|
@ -723,10 +750,10 @@ async fn test_following_to_channel_notes_without_a_shared_project(
|
|||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_collaborators(collaborators: &[proto::Collaborator], ids: &[Option<UserId>]) {
|
||||
fn assert_collaborators(collaborators: &HashMap<PeerId, Collaborator>, ids: &[Option<UserId>]) {
|
||||
assert_eq!(
|
||||
collaborators
|
||||
.into_iter()
|
||||
.values()
|
||||
.map(|collaborator| collaborator.user_id)
|
||||
.collect::<Vec<_>>(),
|
||||
ids.into_iter().map(|id| id.unwrap()).collect::<Vec<_>>()
|
||||
|
|
|
@ -273,7 +273,7 @@ impl RandomizedTest for RandomChannelBufferTest {
|
|||
// channel buffer.
|
||||
let collaborators = channel_buffer.collaborators();
|
||||
let mut user_ids =
|
||||
collaborators.iter().map(|c| c.user_id).collect::<Vec<_>>();
|
||||
collaborators.values().map(|c| c.user_id).collect::<Vec<_>>();
|
||||
user_ids.sort();
|
||||
assert_eq!(
|
||||
user_ids,
|
||||
|
|
|
@ -538,15 +538,7 @@ impl TestClient {
|
|||
root_path: impl AsRef<Path>,
|
||||
cx: &mut TestAppContext,
|
||||
) -> (ModelHandle<Project>, WorktreeId) {
|
||||
let project = cx.update(|cx| {
|
||||
Project::local(
|
||||
self.client().clone(),
|
||||
self.app_state.user_store.clone(),
|
||||
self.app_state.languages.clone(),
|
||||
self.app_state.fs.clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
let project = self.build_empty_local_project(cx);
|
||||
let (worktree, _) = project
|
||||
.update(cx, |p, cx| {
|
||||
p.find_or_create_local_worktree(root_path, true, cx)
|
||||
|
@ -559,6 +551,18 @@ impl TestClient {
|
|||
(project, worktree.read_with(cx, |tree, _| tree.id()))
|
||||
}
|
||||
|
||||
pub fn build_empty_local_project(&self, cx: &mut TestAppContext) -> ModelHandle<Project> {
|
||||
cx.update(|cx| {
|
||||
Project::local(
|
||||
self.client().clone(),
|
||||
self.app_state.user_store.clone(),
|
||||
self.app_state.languages.clone(),
|
||||
self.app_state.fs.clone(),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn build_remote_project(
|
||||
&self,
|
||||
host_project_id: u64,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue