@@ -1,749 +1,442 @@
-use std::ops::Range;
-
-use crate::{
- rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
- tests::TestServer,
-};
-use client::{Collaborator, ParticipantIndex, UserId};
-use collections::HashMap;
-use editor::{Anchor, Editor, ToOffset};
-use futures::future;
-use gpui::{BackgroundExecutor, Model, TestAppContext, ViewContext};
-use rpc::{proto::PeerId, RECEIVE_TIMEOUT};
-
-#[gpui::test]
-async fn test_core_channel_buffers(
- executor: BackgroundExecutor,
- cx_a: &mut TestAppContext,
- cx_b: &mut TestAppContext,
-) {
- let mut server = TestServer::start(executor.clone()).await;
- let client_a = server.create_client(cx_a, "user_a").await;
- let client_b = server.create_client(cx_b, "user_b").await;
-
- let channel_id = server
- .make_channel("zed", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
- .await;
-
- // Client A joins the channel buffer
- let channel_buffer_a = client_a
- .channel_store()
- .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
- .await
- .unwrap();
-
- // Client A edits the buffer
- let buffer_a = channel_buffer_a.read_with(cx_a, |buffer, _| buffer.buffer());
- buffer_a.update(cx_a, |buffer, cx| {
- buffer.edit([(0..0, "hello world")], None, cx)
- });
- buffer_a.update(cx_a, |buffer, cx| {
- buffer.edit([(5..5, ", cruel")], None, cx)
- });
- buffer_a.update(cx_a, |buffer, cx| {
- buffer.edit([(0..5, "goodbye")], None, cx)
- });
- buffer_a.update(cx_a, |buffer, cx| buffer.undo(cx));
- assert_eq!(buffer_text(&buffer_a, cx_a), "hello, cruel world");
- executor.run_until_parked();
-
- // Client B joins the channel buffer
- let channel_buffer_b = client_b
- .channel_store()
- .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
- .await
- .unwrap();
- channel_buffer_b.read_with(cx_b, |buffer, _| {
- assert_collaborators(
- buffer.collaborators(),
- &[client_a.user_id(), client_b.user_id()],
- );
- });
-
- // Client B sees the correct text, and then edits it
- let buffer_b = channel_buffer_b.read_with(cx_b, |buffer, _| buffer.buffer());
- assert_eq!(
- buffer_b.read_with(cx_b, |buffer, _| buffer.remote_id()),
- buffer_a.read_with(cx_a, |buffer, _| buffer.remote_id())
- );
- assert_eq!(buffer_text(&buffer_b, cx_b), "hello, cruel world");
- buffer_b.update(cx_b, |buffer, cx| {
- buffer.edit([(7..12, "beautiful")], None, cx)
- });
-
- // Both A and B see the new edit
- executor.run_until_parked();
- assert_eq!(buffer_text(&buffer_a, cx_a), "hello, beautiful world");
- assert_eq!(buffer_text(&buffer_b, cx_b), "hello, beautiful world");
-
- // Client A closes the channel buffer.
- cx_a.update(|_| drop(channel_buffer_a));
- executor.run_until_parked();
-
- // Client B sees that client A is gone from the channel buffer.
- channel_buffer_b.read_with(cx_b, |buffer, _| {
- assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
- });
-
- // Client A rejoins the channel buffer
- let _channel_buffer_a = client_a
- .channel_store()
- .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
- .await
- .unwrap();
- executor.run_until_parked();
-
- // Sanity test, make sure we saw A rejoining
- channel_buffer_b.read_with(cx_b, |buffer, _| {
- assert_collaborators(
- &buffer.collaborators(),
- &[client_a.user_id(), client_b.user_id()],
- );
- });
-
- // Client A loses connection.
- server.forbid_connections();
- server.disconnect_client(client_a.peer_id().unwrap());
- executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-
- // Client B observes A disconnect
- channel_buffer_b.read_with(cx_b, |buffer, _| {
- assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
- });
-
- // TODO:
- // - Test synchronizing offline updates, what happens to A's channel buffer when A disconnects
- // - Test interaction with channel deletion while buffer is open
-}
-
-// todo!("collab_ui")
+//todo(partially ported)
+// use std::ops::Range;
+
+// use crate::{
+// rpc::{CLEANUP_TIMEOUT, RECONNECT_TIMEOUT},
+// tests::TestServer,
+// };
+// use client::{Collaborator, ParticipantIndex, UserId};
+// use collections::HashMap;
+// use editor::{Anchor, Editor, ToOffset};
+// use futures::future;
+// use gpui::{BackgroundExecutor, Model, TestAppContext, ViewContext};
+// use rpc::{proto::PeerId, RECEIVE_TIMEOUT};
+
// #[gpui::test]
-// async fn test_channel_notes_participant_indices(
+// async fn test_core_channel_buffers(
// executor: BackgroundExecutor,
-// mut cx_a: &mut TestAppContext,
-// mut cx_b: &mut TestAppContext,
-// cx_c: &mut TestAppContext,
+// cx_a: &mut TestAppContext,
+// cx_b: &mut TestAppContext,
// ) {
-// let mut server = TestServer::start(&executor).await;
+// let mut server = TestServer::start(executor.clone()).await;
// let client_a = server.create_client(cx_a, "user_a").await;
// 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",
-// None,
-// (&client_a, cx_a),
-// &mut [(&client_b, cx_b), (&client_c, cx_c)],
-// )
+// .make_channel("zed", None, (&client_a, cx_a), &mut [(&client_b, cx_b)])
// .await;
-// client_a
-// .fs()
-// .insert_tree("/root", json!({"file.txt": "123"}))
-// .await;
-// 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))
+// // Client A joins the channel buffer
+// let channel_buffer_a = client_a
+// .channel_store()
+// .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
// .await
// .unwrap();
-// // 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]);
-// });
-// });
+// // Client A edits the buffer
+// let buffer_a = channel_buffer_a.read_with(cx_a, |buffer, _| buffer.buffer());
+// buffer_a.update(cx_a, |buffer, cx| {
+// buffer.edit([(0..0, "hello world")], None, cx)
// });
-// executor.run_until_parked();
-// 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]);
-// });
-// });
+// buffer_a.update(cx_a, |buffer, cx| {
+// buffer.edit([(5..5, ", cruel")], None, cx)
+// });
+// buffer_a.update(cx_a, |buffer, cx| {
+// buffer.edit([(0..5, "goodbye")], None, cx)
// });
+// buffer_a.update(cx_a, |buffer, cx| buffer.undo(cx));
+// assert_eq!(buffer_text(&buffer_a, cx_a), "hello, cruel world");
// executor.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]);
-// });
-// });
+
+// // Client B joins the channel buffer
+// let channel_buffer_b = client_b
+// .channel_store()
+// .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
+// .await
+// .unwrap();
+// channel_buffer_b.read_with(cx_b, |buffer, _| {
+// assert_collaborators(
+// buffer.collaborators(),
+// &[client_a.user_id(), client_b.user_id()],
+// );
// });
-// // Client A sees clients B and C without assigned colors, because they aren't
-// // in a call together.
-// executor.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);
-// });
+// // Client B sees the correct text, and then edits it
+// let buffer_b = channel_buffer_b.read_with(cx_b, |buffer, _| buffer.buffer());
+// assert_eq!(
+// buffer_b.read_with(cx_b, |buffer, _| buffer.remote_id()),
+// buffer_a.read_with(cx_a, |buffer, _| buffer.remote_id())
+// );
+// assert_eq!(buffer_text(&buffer_b, cx_b), "hello, cruel world");
+// buffer_b.update(cx_b, |buffer, cx| {
+// buffer.edit([(7..12, "beautiful")], None, cx)
// });
-// // 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();
-// }
+// // Both A and B see the new edit
+// executor.run_until_parked();
+// assert_eq!(buffer_text(&buffer_a, cx_a), "hello, beautiful world");
+// assert_eq!(buffer_text(&buffer_b, cx_b), "hello, beautiful world");
-// // Clients A and B see each other with two different assigned colors. Client C
-// // still doesn't have a color.
+// // Client A closes the channel buffer.
+// cx_a.update(|_| drop(channel_buffer_a));
// executor.run_until_parked();
-// channel_view_a.update(cx_a, |notes, cx| {
-// notes.editor.update(cx, |editor, cx| {
-// assert_remote_selections(
-// editor,
-// &[(Some(ParticipantIndex(1)), 1..2), (None, 2..3)],
-// cx,
-// );
-// });
-// });
-// channel_view_b.update(cx_b, |notes, cx| {
-// notes.editor.update(cx, |editor, cx| {
-// assert_remote_selections(
-// editor,
-// &[(Some(ParticipantIndex(0)), 0..1), (None, 2..3)],
-// cx,
-// );
-// });
+
+// // Client B sees that client A is gone from the channel buffer.
+// channel_buffer_b.read_with(cx_b, |buffer, _| {
+// assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
// });
-// // 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))
+// // Client A rejoins the channel buffer
+// let _channel_buffer_a = client_a
+// .channel_store()
+// .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
// .await
// .unwrap();
-// 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);
+// executor.run_until_parked();
-// // 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();
+// // Sanity test, make sure we saw A rejoining
+// channel_buffer_b.read_with(cx_b, |buffer, _| {
+// assert_collaborators(
+// &buffer.collaborators(),
+// &[client_a.user_id(), client_b.user_id()],
+// );
+// });
-// editor_a.update(cx_a, |editor, cx| {
-// editor.change_selections(None, cx, |selections| {
-// selections.select_ranges(vec![0..1]);
-// });
+// // Client A loses connection.
+// server.forbid_connections();
+// server.disconnect_client(client_a.peer_id().unwrap());
+// executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
+
+// // Client B observes A disconnect
+// channel_buffer_b.read_with(cx_b, |buffer, _| {
+// assert_collaborators(&buffer.collaborators(), &[client_b.user_id()]);
// });
-// editor_b.update(cx_b, |editor, cx| {
-// editor.change_selections(None, cx, |selections| {
-// selections.select_ranges(vec![2..3]);
-// });
+
+// // TODO:
+// // - Test synchronizing offline updates, what happens to A's channel buffer when A disconnects
+// // - Test interaction with channel deletion while buffer is open
+// }
+
+// // todo!("collab_ui")
+// // #[gpui::test]
+// // async fn test_channel_notes_participant_indices(
+// // executor: BackgroundExecutor,
+// // mut cx_a: &mut TestAppContext,
+// // mut cx_b: &mut TestAppContext,
+// // cx_c: &mut TestAppContext,
+// // ) {
+// // let mut server = TestServer::start(&executor).await;
+// // let client_a = server.create_client(cx_a, "user_a").await;
+// // 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",
+// // None,
+// // (&client_a, cx_a),
+// // &mut [(&client_b, cx_b), (&client_c, cx_c)],
+// // )
+// // .await;
+
+// // client_a
+// // .fs()
+// // .insert_tree("/root", json!({"file.txt": "123"}))
+// // .await;
+// // 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();
+
+// // // 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]);
+// // });
+// // });
+// // });
+// // executor.run_until_parked();
+// // 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]);
+// // });
+// // });
+// // });
+// // executor.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]);
+// // });
+// // });
+// // });
+
+// // // Client A sees clients B and C without assigned colors, because they aren't
+// // // in a call together.
+// // executor.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);
+// // });
+// // });
+
+// // // 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();
+// // }
+
+// // // Clients A and B see each other with two different assigned colors. Client C
+// // // still doesn't have a color.
+// // executor.run_until_parked();
+// // channel_view_a.update(cx_a, |notes, cx| {
+// // notes.editor.update(cx, |editor, cx| {
+// // assert_remote_selections(
+// // editor,
+// // &[(Some(ParticipantIndex(1)), 1..2), (None, 2..3)],
+// // cx,
+// // );
+// // });
+// // });
+// // channel_view_b.update(cx_b, |notes, cx| {
+// // notes.editor.update(cx, |editor, cx| {
+// // assert_remote_selections(
+// // editor,
+// // &[(Some(ParticipantIndex(0)), 0..1), (None, 2..3)],
+// // 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_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);
+
+// // // 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]);
+// // });
+// // });
+// // editor_b.update(cx_b, |editor, cx| {
+// // editor.change_selections(None, cx, |selections| {
+// // selections.select_ranges(vec![2..3]);
+// // });
+// // });
+// // executor.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(ParticipantIndex(1)), 2..3)], cx);
+// // });
+// // editor_b.update(cx_b, |editor, cx| {
+// // assert_remote_selections(editor, &[(Some(ParticipantIndex(0)), 0..1)], cx);
+// // });
+// // }
+
+// #[track_caller]
+// fn assert_remote_selections(
+// editor: &mut Editor,
+// expected_selections: &[(Option<ParticipantIndex>, 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.participant_index, start..end)
+// })
+// .collect::<Vec<_>>();
+// assert_eq!(
+// remote_selections, expected_selections,
+// "incorrect remote selections"
+// );
+// }
+
+// #[gpui::test]
+// async fn test_multiple_handles_to_channel_buffer(
+// deterministic: BackgroundExecutor,
+// cx_a: &mut TestAppContext,
+// ) {
+// let mut server = TestServer::start(deterministic.clone()).await;
+// let client_a = server.create_client(cx_a, "user_a").await;
+
+// let channel_id = server
+// .make_channel("the-channel", None, (&client_a, cx_a), &mut [])
+// .await;
+
+// let channel_buffer_1 = client_a
+// .channel_store()
+// .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
+// let channel_buffer_2 = client_a
+// .channel_store()
+// .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
+// let channel_buffer_3 = client_a
+// .channel_store()
+// .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
+
+// // All concurrent tasks for opening a channel buffer return the same model handle.
+// let (channel_buffer, channel_buffer_2, channel_buffer_3) =
+// future::try_join3(channel_buffer_1, channel_buffer_2, channel_buffer_3)
+// .await
+// .unwrap();
+// let channel_buffer_model_id = channel_buffer.entity_id();
+// assert_eq!(channel_buffer, channel_buffer_2);
+// assert_eq!(channel_buffer, channel_buffer_3);
+
+// channel_buffer.update(cx_a, |buffer, cx| {
+// buffer.buffer().update(cx, |buffer, cx| {
+// buffer.edit([(0..0, "hello")], None, cx);
+// })
// });
-// executor.run_until_parked();
+// 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(ParticipantIndex(1)), 2..3)], cx);
+// cx_a.update(|_| {
+// drop(channel_buffer);
+// drop(channel_buffer_2);
+// drop(channel_buffer_3);
// });
-// editor_b.update(cx_b, |editor, cx| {
-// assert_remote_selections(editor, &[(Some(ParticipantIndex(0)), 0..1)], cx);
+// deterministic.run_until_parked();
+
+// // The channel buffer can be reopened after dropping it.
+// let channel_buffer = client_a
+// .channel_store()
+// .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
+// .await
+// .unwrap();
+// assert_ne!(channel_buffer.entity_id(), channel_buffer_model_id);
+// channel_buffer.update(cx_a, |buffer, cx| {
+// buffer.buffer().update(cx, |buffer, _| {
+// assert_eq!(buffer.text(), "hello");
+// })
// });
// }
-#[track_caller]
-fn assert_remote_selections(
- editor: &mut Editor,
- expected_selections: &[(Option<ParticipantIndex>, 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.participant_index, start..end)
- })
- .collect::<Vec<_>>();
- assert_eq!(
- remote_selections, expected_selections,
- "incorrect remote selections"
- );
-}
-
-#[gpui::test]
-async fn test_multiple_handles_to_channel_buffer(
- deterministic: BackgroundExecutor,
- cx_a: &mut TestAppContext,
-) {
- let mut server = TestServer::start(deterministic.clone()).await;
- let client_a = server.create_client(cx_a, "user_a").await;
-
- let channel_id = server
- .make_channel("the-channel", None, (&client_a, cx_a), &mut [])
- .await;
-
- let channel_buffer_1 = client_a
- .channel_store()
- .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
- let channel_buffer_2 = client_a
- .channel_store()
- .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
- let channel_buffer_3 = client_a
- .channel_store()
- .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx));
-
- // All concurrent tasks for opening a channel buffer return the same model handle.
- let (channel_buffer, channel_buffer_2, channel_buffer_3) =
- future::try_join3(channel_buffer_1, channel_buffer_2, channel_buffer_3)
- .await
- .unwrap();
- let channel_buffer_model_id = channel_buffer.entity_id();
- assert_eq!(channel_buffer, channel_buffer_2);
- assert_eq!(channel_buffer, channel_buffer_3);
-
- channel_buffer.update(cx_a, |buffer, cx| {
- buffer.buffer().update(cx, |buffer, cx| {
- buffer.edit([(0..0, "hello")], None, cx);
- })
- });
- deterministic.run_until_parked();
-
- cx_a.update(|_| {
- drop(channel_buffer);
- drop(channel_buffer_2);
- drop(channel_buffer_3);
- });
- deterministic.run_until_parked();
-
- // The channel buffer can be reopened after dropping it.
- let channel_buffer = client_a
- .channel_store()
- .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
- .await
- .unwrap();
- assert_ne!(channel_buffer.entity_id(), channel_buffer_model_id);
- channel_buffer.update(cx_a, |buffer, cx| {
- buffer.buffer().update(cx, |buffer, _| {
- assert_eq!(buffer.text(), "hello");
- })
- });
-}
-
-#[gpui::test]
-async fn test_channel_buffer_disconnect(
- deterministic: BackgroundExecutor,
- cx_a: &mut TestAppContext,
- cx_b: &mut TestAppContext,
-) {
- let mut server = TestServer::start(deterministic.clone()).await;
- let client_a = server.create_client(cx_a, "user_a").await;
- let client_b = server.create_client(cx_b, "user_b").await;
-
- let channel_id = server
- .make_channel(
- "the-channel",
- None,
- (&client_a, cx_a),
- &mut [(&client_b, cx_b)],
- )
- .await;
-
- let channel_buffer_a = client_a
- .channel_store()
- .update(cx_a, |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();
-
- server.forbid_connections();
- server.disconnect_client(client_a.peer_id().unwrap());
- deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-
- channel_buffer_a.update(cx_a, |buffer, cx| {
- assert_eq!(buffer.channel(cx).unwrap().name, "the-channel");
- assert!(!buffer.is_connected());
- });
-
- deterministic.run_until_parked();
-
- server.allow_connections();
- deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-
- deterministic.run_until_parked();
-
- client_a
- .channel_store()
- .update(cx_a, |channel_store, _| {
- channel_store.remove_channel(channel_id)
- })
- .await
- .unwrap();
- deterministic.run_until_parked();
-
- // Channel buffer observed the deletion
- channel_buffer_b.update(cx_b, |buffer, cx| {
- assert!(buffer.channel(cx).is_none());
- assert!(!buffer.is_connected());
- });
-}
-
-#[gpui::test]
-async fn test_rejoin_channel_buffer(
- deterministic: BackgroundExecutor,
- cx_a: &mut TestAppContext,
- cx_b: &mut TestAppContext,
-) {
- let mut server = TestServer::start(deterministic.clone()).await;
- let client_a = server.create_client(cx_a, "user_a").await;
- let client_b = server.create_client(cx_b, "user_b").await;
-
- let channel_id = server
- .make_channel(
- "the-channel",
- None,
- (&client_a, cx_a),
- &mut [(&client_b, cx_b)],
- )
- .await;
-
- let channel_buffer_a = client_a
- .channel_store()
- .update(cx_a, |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();
-
- channel_buffer_a.update(cx_a, |buffer, cx| {
- buffer.buffer().update(cx, |buffer, cx| {
- buffer.edit([(0..0, "1")], None, cx);
- })
- });
- deterministic.run_until_parked();
-
- // Client A disconnects.
- server.forbid_connections();
- server.disconnect_client(client_a.peer_id().unwrap());
-
- // Both clients make an edit.
- channel_buffer_a.update(cx_a, |buffer, cx| {
- buffer.buffer().update(cx, |buffer, cx| {
- buffer.edit([(1..1, "2")], None, cx);
- })
- });
- channel_buffer_b.update(cx_b, |buffer, cx| {
- buffer.buffer().update(cx, |buffer, cx| {
- buffer.edit([(0..0, "0")], None, cx);
- })
- });
-
- // Both clients see their own edit.
- deterministic.run_until_parked();
- channel_buffer_a.read_with(cx_a, |buffer, cx| {
- assert_eq!(buffer.buffer().read(cx).text(), "12");
- });
- channel_buffer_b.read_with(cx_b, |buffer, cx| {
- assert_eq!(buffer.buffer().read(cx).text(), "01");
- });
-
- // Client A reconnects. Both clients see each other's edits, and see
- // the same collaborators.
- server.allow_connections();
- deterministic.advance_clock(RECEIVE_TIMEOUT);
- channel_buffer_a.read_with(cx_a, |buffer, cx| {
- assert_eq!(buffer.buffer().read(cx).text(), "012");
- });
- channel_buffer_b.read_with(cx_b, |buffer, cx| {
- assert_eq!(buffer.buffer().read(cx).text(), "012");
- });
-
- channel_buffer_a.read_with(cx_a, |buffer_a, _| {
- channel_buffer_b.read_with(cx_b, |buffer_b, _| {
- assert_eq!(buffer_a.collaborators(), buffer_b.collaborators());
- });
- });
-}
-
-#[gpui::test]
-async fn test_channel_buffers_and_server_restarts(
- deterministic: BackgroundExecutor,
- cx_a: &mut TestAppContext,
- cx_b: &mut TestAppContext,
- cx_c: &mut TestAppContext,
-) {
- let mut server = TestServer::start(deterministic.clone()).await;
- let client_a = server.create_client(cx_a, "user_a").await;
- let client_b = server.create_client(cx_b, "user_b").await;
- let client_c = server.create_client(cx_c, "user_c").await;
-
- let channel_id = server
- .make_channel(
- "the-channel",
- None,
- (&client_a, cx_a),
- &mut [(&client_b, cx_b), (&client_c, cx_c)],
- )
- .await;
-
- let channel_buffer_a = client_a
- .channel_store()
- .update(cx_a, |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_c = client_c
- .channel_store()
- .update(cx_c, |store, cx| store.open_channel_buffer(channel_id, cx))
- .await
- .unwrap();
-
- channel_buffer_a.update(cx_a, |buffer, cx| {
- buffer.buffer().update(cx, |buffer, cx| {
- buffer.edit([(0..0, "1")], None, cx);
- })
- });
- deterministic.run_until_parked();
-
- // Client C can't reconnect.
- client_c.override_establish_connection(|_, cx| cx.spawn(|_| future::pending()));
-
- // Server stops.
- server.reset().await;
- deterministic.advance_clock(RECEIVE_TIMEOUT);
-
- // While the server is down, both clients make an edit.
- channel_buffer_a.update(cx_a, |buffer, cx| {
- buffer.buffer().update(cx, |buffer, cx| {
- buffer.edit([(1..1, "2")], None, cx);
- })
- });
- channel_buffer_b.update(cx_b, |buffer, cx| {
- buffer.buffer().update(cx, |buffer, cx| {
- buffer.edit([(0..0, "0")], None, cx);
- })
- });
-
- // Server restarts.
- server.start().await.unwrap();
- deterministic.advance_clock(CLEANUP_TIMEOUT);
-
- // Clients reconnects. Clients A and B see each other's edits, and see
- // that client C has disconnected.
- channel_buffer_a.read_with(cx_a, |buffer, cx| {
- assert_eq!(buffer.buffer().read(cx).text(), "012");
- });
- channel_buffer_b.read_with(cx_b, |buffer, cx| {
- assert_eq!(buffer.buffer().read(cx).text(), "012");
- });
-
- channel_buffer_a.read_with(cx_a, |buffer_a, _| {
- channel_buffer_b.read_with(cx_b, |buffer_b, _| {
- assert_collaborators(
- buffer_a.collaborators(),
- &[client_a.user_id(), client_b.user_id()],
- );
- assert_eq!(buffer_a.collaborators(), buffer_b.collaborators());
- });
- });
-}
-
-//todo!(collab_ui)
-// #[gpui::test(iterations = 10)]
-// async fn test_following_to_channel_notes_without_a_shared_project(
+// #[gpui::test]
+// async fn test_channel_buffer_disconnect(
// deterministic: BackgroundExecutor,
-// mut cx_a: &mut TestAppContext,
-// mut cx_b: &mut TestAppContext,
-// mut cx_c: &mut TestAppContext,
+// cx_a: &mut TestAppContext,
+// cx_b: &mut TestAppContext,
// ) {
-// let mut server = TestServer::start(&deterministic).await;
+// let mut server = TestServer::start(deterministic.clone()).await;
// let client_a = server.create_client(cx_a, "user_a").await;
// let client_b = server.create_client(cx_b, "user_b").await;
-// let client_c = server.create_client(cx_c, "user_c").await;
-
-// cx_a.update(editor::init);
-// cx_b.update(editor::init);
-// cx_c.update(editor::init);
-// cx_a.update(collab_ui::channel_view::init);
-// cx_b.update(collab_ui::channel_view::init);
-// cx_c.update(collab_ui::channel_view::init);
-
-// let channel_1_id = server
-// .make_channel(
-// "channel-1",
-// None,
-// (&client_a, cx_a),
-// &mut [(&client_b, cx_b), (&client_c, cx_c)],
-// )
-// .await;
-// let channel_2_id = server
+// let channel_id = server
// .make_channel(
-// "channel-2",
+// "the-channel",
// None,
// (&client_a, cx_a),
-// &mut [(&client_b, cx_b), (&client_c, cx_c)],
+// &mut [(&client_b, cx_b)],
// )
// .await;
-// // Clients A, B, and C join a channel.
-// 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);
-// for (call, cx) in [
-// (&active_call_a, &mut cx_a),
-// (&active_call_b, &mut cx_b),
-// (&active_call_c, &mut cx_c),
-// ] {
-// call.update(*cx, |call, cx| call.join_channel(channel_1_id, cx))
-// .await
-// .unwrap();
-// }
-// deterministic.run_until_parked();
-
-// // Clients A, B, and C all open their own unshared projects.
-// client_a.fs().insert_tree("/a", json!({})).await;
-// client_b.fs().insert_tree("/b", json!({})).await;
-// client_c.fs().insert_tree("/c", json!({})).await;
-// let (project_a, _) = client_a.build_local_project("/a", cx_a).await;
-// let (project_b, _) = client_b.build_local_project("/b", cx_b).await;
-// let (project_c, _) = client_b.build_local_project("/c", cx_c).await;
-// 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);
-
-// active_call_a
-// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+// let channel_buffer_a = client_a
+// .channel_store()
+// .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx))
// .await
// .unwrap();
-// // Client A opens the notes for channel 1.
-// let channel_view_1_a = cx_a
-// .update(|cx| ChannelView::open(channel_1_id, workspace_a.clone(), cx))
+// let channel_buffer_b = client_b
+// .channel_store()
+// .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx))
// .await
// .unwrap();
-// channel_view_1_a.update(cx_a, |notes, cx| {
-// assert_eq!(notes.channel(cx).unwrap().name, "channel-1");
-// notes.editor.update(cx, |editor, cx| {
-// editor.insert("Hello from A.", cx);
-// editor.change_selections(None, cx, |selections| {
-// selections.select_ranges(vec![3..4]);
-// });
-// });
+
+// server.forbid_connections();
+// server.disconnect_client(client_a.peer_id().unwrap());
+// deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
+
+// channel_buffer_a.update(cx_a, |buffer, cx| {
+// assert_eq!(buffer.channel(cx).unwrap().name, "the-channel");
+// assert!(!buffer.is_connected());
// });
-// // Client B follows client A.
-// workspace_b
-// .update(cx_b, |workspace, cx| {
-// workspace.follow(client_a.peer_id().unwrap(), cx).unwrap()
-// })
-// .await
-// .unwrap();
+// deterministic.run_until_parked();
+
+// server.allow_connections();
+// deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-// // Client B is taken to the notes for channel 1, with the same
-// // text selected as client A.
// deterministic.run_until_parked();
-// let channel_view_1_b = workspace_b.read_with(cx_b, |workspace, cx| {
-// assert_eq!(
-// workspace.leader_for_pane(workspace.active_pane()),
-// Some(client_a.peer_id().unwrap())
-// );
-// workspace
-// .active_item(cx)
-// .expect("no active item")
-// .downcast::<ChannelView>()
-// .expect("active item is not a channel view")
-// });
-// channel_view_1_b.read_with(cx_b, |notes, cx| {
-// assert_eq!(notes.channel(cx).unwrap().name, "channel-1");
-// let editor = notes.editor.read(cx);
-// assert_eq!(editor.text(cx), "Hello from A.");
-// assert_eq!(editor.selections.ranges::<usize>(cx), &[3..4]);
-// });
-// // Client A opens the notes for channel 2.
-// let channel_view_2_a = cx_a
-// .update(|cx| ChannelView::open(channel_2_id, workspace_a.clone(), cx))
+// client_a
+// .channel_store()
+// .update(cx_a, |channel_store, _| {
+// channel_store.remove_channel(channel_id)
+// })
// .await
// .unwrap();
-// channel_view_2_a.read_with(cx_a, |notes, cx| {
-// assert_eq!(notes.channel(cx).unwrap().name, "channel-2");
-// });
-
-// // Client B is taken to the notes for channel 2.
// deterministic.run_until_parked();
-// let channel_view_2_b = workspace_b.read_with(cx_b, |workspace, cx| {
-// assert_eq!(
-// workspace.leader_for_pane(workspace.active_pane()),
-// Some(client_a.peer_id().unwrap())
-// );
-// workspace
-// .active_item(cx)
-// .expect("no active item")
-// .downcast::<ChannelView>()
-// .expect("active item is not a channel view")
-// });
-// channel_view_2_b.read_with(cx_b, |notes, cx| {
-// assert_eq!(notes.channel(cx).unwrap().name, "channel-2");
+
+// // Channel buffer observed the deletion
+// channel_buffer_b.update(cx_b, |buffer, cx| {
+// assert!(buffer.channel(cx).is_none());
+// assert!(!buffer.is_connected());
// });
// }
-//todo!(collab_ui)
// #[gpui::test]
-// async fn test_channel_buffer_changes(
+// async fn test_rejoin_channel_buffer(
// deterministic: BackgroundExecutor,
// cx_a: &mut TestAppContext,
// cx_b: &mut TestAppContext,
// ) {
-// let mut server = TestServer::start(&deterministic).await;
+// let mut server = TestServer::start(deterministic.clone()).await;
// let client_a = server.create_client(cx_a, "user_a").await;
// let client_b = server.create_client(cx_b, "user_b").await;
@@ -1,1888 +1,1889 @@
-use std::{
- path::Path,
- sync::{
- atomic::{self, AtomicBool, AtomicUsize},
- Arc,
- },
-};
-
-use call::ActiveCall;
-use editor::{
- test::editor_test_context::{AssertionContextManager, EditorTestContext},
- Anchor, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Redo, Rename,
- ToggleCodeActions, Undo,
-};
-use gpui::{BackgroundExecutor, TestAppContext, VisualContext, VisualTestContext};
-use indoc::indoc;
-use language::{
- language_settings::{AllLanguageSettings, InlayHintSettings},
- tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig,
-};
-use rpc::RECEIVE_TIMEOUT;
-use serde_json::json;
-use settings::SettingsStore;
-use text::Point;
-use workspace::Workspace;
-
-use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
-
-#[gpui::test(iterations = 10)]
-async fn test_host_disconnect(
- executor: BackgroundExecutor,
- cx_a: &mut TestAppContext,
- cx_b: &mut TestAppContext,
- cx_c: &mut TestAppContext,
-) {
- let mut server = TestServer::start(executor).await;
- let client_a = server.create_client(cx_a, "user_a").await;
- let client_b = server.create_client(cx_b, "user_b").await;
- let client_c = server.create_client(cx_c, "user_c").await;
- server
- .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
- .await;
-
- cx_b.update(editor::init);
-
- client_a
- .fs()
- .insert_tree(
- "/a",
- serde_json::json!({
- "a.txt": "a-contents",
- "b.txt": "b-contents",
- }),
- )
- .await;
-
- let active_call_a = cx_a.read(ActiveCall::global);
- let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-
- let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees().next().unwrap());
- let project_id = active_call_a
- .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
- .await
- .unwrap();
-
- let project_b = client_b.build_remote_project(project_id, cx_b).await;
- executor.run_until_parked();
-
- assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
-
- let workspace_b =
- cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
- let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
-
- let editor_b = workspace_b
- .update(cx_b, |workspace, cx| {
- workspace.open_path((worktree_id, "b.txt"), None, true, cx)
- })
- .unwrap()
- .await
- .unwrap()
- .downcast::<Editor>()
- .unwrap();
-
- //TODO: focus
- assert!(cx_b.update_view(&editor_b, |editor, cx| editor.is_focused(cx)));
- editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
- //todo(is_edited)
- // assert!(workspace_b.is_edited(cx_b));
-
- // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
- server.forbid_connections();
- server.disconnect_client(client_a.peer_id().unwrap());
- executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-
- project_a.read_with(cx_a, |project, _| project.collaborators().is_empty());
-
- project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
-
- project_b.read_with(cx_b, |project, _| project.is_read_only());
-
- assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
-
- // Ensure client B's edited state is reset and that the whole window is blurred.
-
- workspace_b.update(cx_b, |_, cx| {
- assert_eq!(cx.focused_view_id(), None);
- });
- // assert!(!workspace_b.is_edited(cx_b));
-
- // Ensure client B is not prompted to save edits when closing window after disconnecting.
- let can_close = workspace_b
- .update(cx_b, |workspace, cx| workspace.prepare_to_close(true, cx))
- .await
- .unwrap();
- assert!(can_close);
-
- // Allow client A to reconnect to the server.
- server.allow_connections();
- executor.advance_clock(RECEIVE_TIMEOUT);
-
- // Client B calls client A again after they reconnected.
- let active_call_b = cx_b.read(ActiveCall::global);
- active_call_b
- .update(cx_b, |call, cx| {
- call.invite(client_a.user_id().unwrap(), None, cx)
- })
- .await
- .unwrap();
- executor.run_until_parked();
- active_call_a
- .update(cx_a, |call, cx| call.accept_incoming(cx))
- .await
- .unwrap();
-
- active_call_a
- .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
- .await
- .unwrap();
-
- // Drop client A's connection again. We should still unshare it successfully.
- server.forbid_connections();
- server.disconnect_client(client_a.peer_id().unwrap());
- executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
-
- project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
-}
-
-#[gpui::test]
-async fn test_newline_above_or_below_does_not_move_guest_cursor(
- executor: BackgroundExecutor,
- cx_a: &mut TestAppContext,
- cx_b: &mut TestAppContext,
-) {
- let mut server = TestServer::start(&executor).await;
- let client_a = server.create_client(cx_a, "user_a").await;
- let client_b = server.create_client(cx_b, "user_b").await;
- server
- .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
- .await;
- let active_call_a = cx_a.read(ActiveCall::global);
-
- client_a
- .fs()
- .insert_tree("/dir", json!({ "a.txt": "Some text\n" }))
- .await;
- let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
- let project_id = active_call_a
- .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
- .await
- .unwrap();
-
- let project_b = client_b.build_remote_project(project_id, cx_b).await;
-
- // Open a buffer as client A
- let buffer_a = project_a
- .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
- .await
- .unwrap();
- let window_a = cx_a.add_empty_window();
- let editor_a =
- window_a.build_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
- let mut editor_cx_a = EditorTestContext {
- cx: cx_a,
- window: window_a.into(),
- editor: editor_a,
- assertion_cx: AssertionContextManager::new(),
- };
-
- // Open a buffer as client B
- let buffer_b = project_b
- .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
- .await
- .unwrap();
- let window_b = cx_b.add_empty_window();
- let editor_b =
- window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
- let mut editor_cx_b = EditorTestContext {
- cx: cx_b,
- window: window_b.into(),
- editor: editor_b,
- assertion_cx: AssertionContextManager::new(),
- };
-
- // Test newline above
- editor_cx_a.set_selections_state(indoc! {"
- Some textĖ
- "});
- editor_cx_b.set_selections_state(indoc! {"
- Some textĖ
- "});
- editor_cx_a.update_editor(|editor, cx| editor.newline_above(&editor::NewlineAbove, cx));
- executor.run_until_parked();
- editor_cx_a.assert_editor_state(indoc! {"
- Ė
- Some text
- "});
- editor_cx_b.assert_editor_state(indoc! {"
-
- Some textĖ
- "});
-
- // Test newline below
- editor_cx_a.set_selections_state(indoc! {"
-
- Some textĖ
- "});
- editor_cx_b.set_selections_state(indoc! {"
-
- Some textĖ
- "});
- editor_cx_a.update_editor(|editor, cx| editor.newline_below(&editor::NewlineBelow, cx));
- executor.run_until_parked();
- editor_cx_a.assert_editor_state(indoc! {"
-
- Some text
- Ė
- "});
- editor_cx_b.assert_editor_state(indoc! {"
-
- Some textĖ
-
- "});
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_collaborating_with_completion(
- executor: BackgroundExecutor,
- cx_a: &mut TestAppContext,
- cx_b: &mut TestAppContext,
-) {
- let mut server = TestServer::start(&executor).await;
- let client_a = server.create_client(cx_a, "user_a").await;
- let client_b = server.create_client(cx_b, "user_b").await;
- server
- .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
- .await;
- let active_call_a = cx_a.read(ActiveCall::global);
-
- // Set up a fake language server.
- let mut language = Language::new(
- LanguageConfig {
- name: "Rust".into(),
- path_suffixes: vec!["rs".to_string()],
- ..Default::default()
- },
- Some(tree_sitter_rust::language()),
- );
- let mut fake_language_servers = language
- .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
- capabilities: lsp::ServerCapabilities {
- completion_provider: Some(lsp::CompletionOptions {
- trigger_characters: Some(vec![".".to_string()]),
- resolve_provider: Some(true),
- ..Default::default()
- }),
- ..Default::default()
- },
- ..Default::default()
- }))
- .await;
- client_a.language_registry().add(Arc::new(language));
-
- client_a
- .fs()
- .insert_tree(
- "/a",
- json!({
- "main.rs": "fn main() { a }",
- "other.rs": "",
- }),
- )
- .await;
- let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
- let project_id = active_call_a
- .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
- .await
- .unwrap();
- let project_b = client_b.build_remote_project(project_id, cx_b).await;
-
- // Open a file in an editor as the guest.
- let buffer_b = project_b
- .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
- .await
- .unwrap();
- let window_b = cx_b.add_empty_window();
- let editor_b = window_b.build_view(cx_b, |cx| {
- Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
- });
-
- let fake_language_server = fake_language_servers.next().await.unwrap();
- cx_a.foreground().run_until_parked();
-
- buffer_b.read_with(cx_b, |buffer, _| {
- assert!(!buffer.completion_triggers().is_empty())
- });
-
- // Type a completion trigger character as the guest.
- editor_b.update(cx_b, |editor, cx| {
- editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
- editor.handle_input(".", cx);
- cx.focus(&editor_b);
- });
-
- // Receive a completion request as the host's language server.
- // Return some completions from the host's language server.
- cx_a.foreground().start_waiting();
- fake_language_server
- .handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
- assert_eq!(
- params.text_document_position.text_document.uri,
- lsp::Url::from_file_path("/a/main.rs").unwrap(),
- );
- assert_eq!(
- params.text_document_position.position,
- lsp::Position::new(0, 14),
- );
-
- Ok(Some(lsp::CompletionResponse::Array(vec![
- lsp::CompletionItem {
- label: "first_method(ā¦)".into(),
- detail: Some("fn(&mut self, B) -> C".into()),
- text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
- new_text: "first_method($1)".to_string(),
- range: lsp::Range::new(
- lsp::Position::new(0, 14),
- lsp::Position::new(0, 14),
- ),
- })),
- insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
- ..Default::default()
- },
- lsp::CompletionItem {
- label: "second_method(ā¦)".into(),
- detail: Some("fn(&mut self, C) -> D<E>".into()),
- text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
- new_text: "second_method()".to_string(),
- range: lsp::Range::new(
- lsp::Position::new(0, 14),
- lsp::Position::new(0, 14),
- ),
- })),
- insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
- ..Default::default()
- },
- ])))
- })
- .next()
- .await
- .unwrap();
- cx_a.foreground().finish_waiting();
-
- // Open the buffer on the host.
- let buffer_a = project_a
- .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
- .await
- .unwrap();
- cx_a.foreground().run_until_parked();
-
- buffer_a.read_with(cx_a, |buffer, _| {
- assert_eq!(buffer.text(), "fn main() { a. }")
- });
-
- // Confirm a completion on the guest.
-
- editor_b.read_with(cx_b, |editor, _| assert!(editor.context_menu_visible()));
- editor_b.update(cx_b, |editor, cx| {
- editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
- assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
- });
-
- // Return a resolved completion from the host's language server.
- // The resolved completion has an additional text edit.
- fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
- |params, _| async move {
- assert_eq!(params.label, "first_method(ā¦)");
- Ok(lsp::CompletionItem {
- label: "first_method(ā¦)".into(),
- detail: Some("fn(&mut self, B) -> C".into()),
- text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
- new_text: "first_method($1)".to_string(),
- range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
- })),
- additional_text_edits: Some(vec![lsp::TextEdit {
- new_text: "use d::SomeTrait;\n".to_string(),
- range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
- }]),
- insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
- ..Default::default()
- })
- },
- );
-
- // The additional edit is applied.
- cx_a.executor().run_until_parked();
-
- buffer_a.read_with(cx_a, |buffer, _| {
- assert_eq!(
- buffer.text(),
- "use d::SomeTrait;\nfn main() { a.first_method() }"
- );
- });
-
- buffer_b.read_with(cx_b, |buffer, _| {
- assert_eq!(
- buffer.text(),
- "use d::SomeTrait;\nfn main() { a.first_method() }"
- );
- });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_collaborating_with_code_actions(
- executor: BackgroundExecutor,
- cx_a: &mut TestAppContext,
- cx_b: &mut TestAppContext,
-) {
- let mut server = TestServer::start(&executor).await;
- let client_a = server.create_client(cx_a, "user_a").await;
- //
- let client_b = server.create_client(cx_b, "user_b").await;
- server
- .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
- .await;
- let active_call_a = cx_a.read(ActiveCall::global);
-
- cx_b.update(editor::init);
-
- // Set up a fake language server.
- let mut language = Language::new(
- LanguageConfig {
- name: "Rust".into(),
- path_suffixes: vec!["rs".to_string()],
- ..Default::default()
- },
- Some(tree_sitter_rust::language()),
- );
- let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
- client_a.language_registry().add(Arc::new(language));
-
- client_a
- .fs()
- .insert_tree(
- "/a",
- json!({
- "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
- "other.rs": "pub fn foo() -> usize { 4 }",
- }),
- )
- .await;
- let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
- let project_id = active_call_a
- .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
- .await
- .unwrap();
-
- // Join the project as client B.
- let project_b = client_b.build_remote_project(project_id, cx_b).await;
- let window_b =
- cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
- let workspace_b = window_b.root(cx_b);
- let editor_b = workspace_b
- .update(cx_b, |workspace, cx| {
- workspace.open_path((worktree_id, "main.rs"), None, true, cx)
- })
- .await
- .unwrap()
- .downcast::<Editor>()
- .unwrap();
-
- let mut fake_language_server = fake_language_servers.next().await.unwrap();
- let mut requests = fake_language_server
- .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
- assert_eq!(
- params.text_document.uri,
- lsp::Url::from_file_path("/a/main.rs").unwrap(),
- );
- assert_eq!(params.range.start, lsp::Position::new(0, 0));
- assert_eq!(params.range.end, lsp::Position::new(0, 0));
- Ok(None)
- });
- executor.advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
- requests.next().await;
-
- // Move cursor to a location that contains code actions.
- editor_b.update(cx_b, |editor, cx| {
- editor.change_selections(None, cx, |s| {
- s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
- });
- cx.focus(&editor_b);
- });
-
- let mut requests = fake_language_server
- .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
- assert_eq!(
- params.text_document.uri,
- lsp::Url::from_file_path("/a/main.rs").unwrap(),
- );
- assert_eq!(params.range.start, lsp::Position::new(1, 31));
- assert_eq!(params.range.end, lsp::Position::new(1, 31));
-
- Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
- lsp::CodeAction {
- title: "Inline into all callers".to_string(),
- edit: Some(lsp::WorkspaceEdit {
- changes: Some(
- [
- (
- lsp::Url::from_file_path("/a/main.rs").unwrap(),
- vec![lsp::TextEdit::new(
- lsp::Range::new(
- lsp::Position::new(1, 22),
- lsp::Position::new(1, 34),
- ),
- "4".to_string(),
- )],
- ),
- (
- lsp::Url::from_file_path("/a/other.rs").unwrap(),
- vec![lsp::TextEdit::new(
- lsp::Range::new(
- lsp::Position::new(0, 0),
- lsp::Position::new(0, 27),
- ),
- "".to_string(),
- )],
- ),
- ]
- .into_iter()
- .collect(),
- ),
- ..Default::default()
- }),
- data: Some(json!({
- "codeActionParams": {
- "range": {
- "start": {"line": 1, "column": 31},
- "end": {"line": 1, "column": 31},
- }
- }
- })),
- ..Default::default()
- },
- )]))
- });
- executor.advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
- requests.next().await;
-
- // Toggle code actions and wait for them to display.
- editor_b.update(cx_b, |editor, cx| {
- editor.toggle_code_actions(
- &ToggleCodeActions {
- deployed_from_indicator: false,
- },
- cx,
- );
- });
- cx_a.foreground().run_until_parked();
-
- editor_b.read_with(cx_b, |editor, _| assert!(editor.context_menu_visible()));
-
- fake_language_server.remove_request_handler::<lsp::request::CodeActionRequest>();
-
- // Confirming the code action will trigger a resolve request.
- let confirm_action = workspace_b
- .update(cx_b, |workspace, cx| {
- Editor::confirm_code_action(workspace, &ConfirmCodeAction { item_ix: Some(0) }, cx)
- })
- .unwrap();
- fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
- |_, _| async move {
- Ok(lsp::CodeAction {
- title: "Inline into all callers".to_string(),
- edit: Some(lsp::WorkspaceEdit {
- changes: Some(
- [
- (
- lsp::Url::from_file_path("/a/main.rs").unwrap(),
- vec![lsp::TextEdit::new(
- lsp::Range::new(
- lsp::Position::new(1, 22),
- lsp::Position::new(1, 34),
- ),
- "4".to_string(),
- )],
- ),
- (
- lsp::Url::from_file_path("/a/other.rs").unwrap(),
- vec![lsp::TextEdit::new(
- lsp::Range::new(
- lsp::Position::new(0, 0),
- lsp::Position::new(0, 27),
- ),
- "".to_string(),
- )],
- ),
- ]
- .into_iter()
- .collect(),
- ),
- ..Default::default()
- }),
- ..Default::default()
- })
- },
- );
-
- // After the action is confirmed, an editor containing both modified files is opened.
- confirm_action.await.unwrap();
-
- let code_action_editor = workspace_b.read_with(cx_b, |workspace, cx| {
- workspace
- .active_item(cx)
- .unwrap()
- .downcast::<Editor>()
- .unwrap()
- });
- code_action_editor.update(cx_b, |editor, cx| {
- assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
- editor.undo(&Undo, cx);
- assert_eq!(
- editor.text(cx),
- "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
- );
- editor.redo(&Redo, cx);
- assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
- });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_collaborating_with_renames(
- executor: BackgroundExecutor,
- cx_a: &mut TestAppContext,
- cx_b: &mut TestAppContext,
-) {
- let mut server = TestServer::start(&executor).await;
- let client_a = server.create_client(cx_a, "user_a").await;
- let client_b = server.create_client(cx_b, "user_b").await;
- server
- .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
- .await;
- let active_call_a = cx_a.read(ActiveCall::global);
-
- cx_b.update(editor::init);
-
- // Set up a fake language server.
- let mut language = Language::new(
- LanguageConfig {
- name: "Rust".into(),
- path_suffixes: vec!["rs".to_string()],
- ..Default::default()
- },
- Some(tree_sitter_rust::language()),
- );
- let mut fake_language_servers = language
- .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
- capabilities: lsp::ServerCapabilities {
- rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
- prepare_provider: Some(true),
- work_done_progress_options: Default::default(),
- })),
- ..Default::default()
- },
- ..Default::default()
- }))
- .await;
- client_a.language_registry().add(Arc::new(language));
-
- client_a
- .fs()
- .insert_tree(
- "/dir",
- json!({
- "one.rs": "const ONE: usize = 1;",
- "two.rs": "const TWO: usize = one::ONE + one::ONE;"
- }),
- )
- .await;
- let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
- let project_id = active_call_a
- .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
- .await
- .unwrap();
- let project_b = client_b.build_remote_project(project_id, cx_b).await;
-
- let window_b =
- cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
- let workspace_b = window_b.root(cx_b);
- let editor_b = workspace_b
- .update(cx_b, |workspace, cx| {
- workspace.open_path((worktree_id, "one.rs"), None, true, cx)
- })
- .await
- .unwrap()
- .downcast::<Editor>()
- .unwrap();
- let fake_language_server = fake_language_servers.next().await.unwrap();
-
- // Move cursor to a location that can be renamed.
- let prepare_rename = editor_b.update(cx_b, |editor, cx| {
- editor.change_selections(None, cx, |s| s.select_ranges([7..7]));
- editor.rename(&Rename, cx).unwrap()
- });
-
- fake_language_server
- .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
- assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
- assert_eq!(params.position, lsp::Position::new(0, 7));
- Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
- lsp::Position::new(0, 6),
- lsp::Position::new(0, 9),
- ))))
- })
- .next()
- .await
- .unwrap();
- prepare_rename.await.unwrap();
- editor_b.update(cx_b, |editor, cx| {
- use editor::ToOffset;
- let rename = editor.pending_rename().unwrap();
- let buffer = editor.buffer().read(cx).snapshot(cx);
- assert_eq!(
- rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
- 6..9
- );
- rename.editor.update(cx, |rename_editor, cx| {
- rename_editor.buffer().update(cx, |rename_buffer, cx| {
- rename_buffer.edit([(0..3, "THREE")], None, cx);
- });
- });
- });
-
- let confirm_rename = workspace_b.update(cx_b, |workspace, cx| {
- Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap()
- });
- fake_language_server
- .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
- assert_eq!(
- params.text_document_position.text_document.uri.as_str(),
- "file:///dir/one.rs"
- );
- assert_eq!(
- params.text_document_position.position,
- lsp::Position::new(0, 6)
- );
- assert_eq!(params.new_name, "THREE");
- Ok(Some(lsp::WorkspaceEdit {
- changes: Some(
- [
- (
- lsp::Url::from_file_path("/dir/one.rs").unwrap(),
- vec![lsp::TextEdit::new(
- lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
- "THREE".to_string(),
- )],
- ),
- (
- lsp::Url::from_file_path("/dir/two.rs").unwrap(),
- vec![
- lsp::TextEdit::new(
- lsp::Range::new(
- lsp::Position::new(0, 24),
- lsp::Position::new(0, 27),
- ),
- "THREE".to_string(),
- ),
- lsp::TextEdit::new(
- lsp::Range::new(
- lsp::Position::new(0, 35),
- lsp::Position::new(0, 38),
- ),
- "THREE".to_string(),
- ),
- ],
- ),
- ]
- .into_iter()
- .collect(),
- ),
- ..Default::default()
- }))
- })
- .next()
- .await
- .unwrap();
- confirm_rename.await.unwrap();
-
- let rename_editor = workspace_b.read_with(cx_b, |workspace, cx| {
- workspace
- .active_item(cx)
- .unwrap()
- .downcast::<Editor>()
- .unwrap()
- });
- rename_editor.update(cx_b, |editor, cx| {
- assert_eq!(
- editor.text(cx),
- "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
- );
- editor.undo(&Undo, cx);
- assert_eq!(
- editor.text(cx),
- "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
- );
- editor.redo(&Redo, cx);
- assert_eq!(
- editor.text(cx),
- "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
- );
- });
-
- // Ensure temporary rename edits cannot be undone/redone.
- editor_b.update(cx_b, |editor, cx| {
- editor.undo(&Undo, cx);
- assert_eq!(editor.text(cx), "const ONE: usize = 1;");
- editor.undo(&Undo, cx);
- assert_eq!(editor.text(cx), "const ONE: usize = 1;");
- editor.redo(&Redo, cx);
- assert_eq!(editor.text(cx), "const THREE: usize = 1;");
- })
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_language_server_statuses(
- executor: BackgroundExecutor,
- cx_a: &mut TestAppContext,
- cx_b: &mut TestAppContext,
-) {
- let mut server = TestServer::start(&executor).await;
- let client_a = server.create_client(cx_a, "user_a").await;
- let client_b = server.create_client(cx_b, "user_b").await;
- server
- .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
- .await;
- let active_call_a = cx_a.read(ActiveCall::global);
-
- cx_b.update(editor::init);
-
- // Set up a fake language server.
- let mut language = Language::new(
- LanguageConfig {
- name: "Rust".into(),
- path_suffixes: vec!["rs".to_string()],
- ..Default::default()
- },
- Some(tree_sitter_rust::language()),
- );
- let mut fake_language_servers = language
- .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
- name: "the-language-server",
- ..Default::default()
- }))
- .await;
- client_a.language_registry().add(Arc::new(language));
-
- client_a
- .fs()
- .insert_tree(
- "/dir",
- json!({
- "main.rs": "const ONE: usize = 1;",
- }),
- )
- .await;
- let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
-
- let _buffer_a = project_a
- .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
- .await
- .unwrap();
-
- let fake_language_server = fake_language_servers.next().await.unwrap();
- fake_language_server.start_progress("the-token").await;
- fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
- token: lsp::NumberOrString::String("the-token".to_string()),
- value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
- lsp::WorkDoneProgressReport {
- message: Some("the-message".to_string()),
- ..Default::default()
- },
- )),
- });
- executor.run_until_parked();
-
- project_a.read_with(cx_a, |project, _| {
- let status = project.language_server_statuses().next().unwrap();
- assert_eq!(status.name, "the-language-server");
- assert_eq!(status.pending_work.len(), 1);
- assert_eq!(
- status.pending_work["the-token"].message.as_ref().unwrap(),
- "the-message"
- );
- });
-
- let project_id = active_call_a
- .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
- .await
- .unwrap();
- executor.run_until_parked();
- let project_b = client_b.build_remote_project(project_id, cx_b).await;
-
- project_b.read_with(cx_b, |project, _| {
- let status = project.language_server_statuses().next().unwrap();
- assert_eq!(status.name, "the-language-server");
- });
-
- fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
- token: lsp::NumberOrString::String("the-token".to_string()),
- value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
- lsp::WorkDoneProgressReport {
- message: Some("the-message-2".to_string()),
- ..Default::default()
- },
- )),
- });
- executor.run_until_parked();
-
- project_a.read_with(cx_a, |project, _| {
- let status = project.language_server_statuses().next().unwrap();
- assert_eq!(status.name, "the-language-server");
- assert_eq!(status.pending_work.len(), 1);
- assert_eq!(
- status.pending_work["the-token"].message.as_ref().unwrap(),
- "the-message-2"
- );
- });
-
- project_b.read_with(cx_b, |project, _| {
- let status = project.language_server_statuses().next().unwrap();
- assert_eq!(status.name, "the-language-server");
- assert_eq!(status.pending_work.len(), 1);
- assert_eq!(
- status.pending_work["the-token"].message.as_ref().unwrap(),
- "the-message-2"
- );
- });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_share_project(
- executor: BackgroundExecutor,
- cx_a: &mut TestAppContext,
- cx_b: &mut TestAppContext,
- cx_c: &mut TestAppContext,
-) {
- let window_b = cx_b.add_empty_window();
- let mut server = TestServer::start(executor).await;
- let client_a = server.create_client(cx_a, "user_a").await;
- let client_b = server.create_client(cx_b, "user_b").await;
- let client_c = server.create_client(cx_c, "user_c").await;
- server
- .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
- .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);
-
- client_a
- .fs()
- .insert_tree(
- "/a",
- json!({
- ".gitignore": "ignored-dir",
- "a.txt": "a-contents",
- "b.txt": "b-contents",
- "ignored-dir": {
- "c.txt": "",
- "d.txt": "",
- }
- }),
- )
- .await;
-
- // Invite client B to collaborate on a project
- let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
- active_call_a
- .update(cx_a, |call, cx| {
- call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
- })
- .await
- .unwrap();
-
- // Join that project as client B
-
- let incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
- executor.run_until_parked();
- let call = incoming_call_b.borrow().clone().unwrap();
- assert_eq!(call.calling_user.github_login, "user_a");
- let initial_project = call.initial_project.unwrap();
- active_call_b
- .update(cx_b, |call, cx| call.accept_incoming(cx))
- .await
- .unwrap();
- let client_b_peer_id = client_b.peer_id().unwrap();
- let project_b = client_b
- .build_remote_project(initial_project.id, cx_b)
- .await;
-
- let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
-
- executor.run_until_parked();
-
- project_a.read_with(cx_a, |project, _| {
- let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
- assert_eq!(client_b_collaborator.replica_id, replica_id_b);
- });
-
- project_b.read_with(cx_b, |project, cx| {
- let worktree = project.worktrees().next().unwrap().read(cx);
- assert_eq!(
- worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
- [
- Path::new(".gitignore"),
- Path::new("a.txt"),
- Path::new("b.txt"),
- Path::new("ignored-dir"),
- ]
- );
- });
-
- project_b
- .update(cx_b, |project, cx| {
- let worktree = project.worktrees().next().unwrap();
- let entry = worktree.read(cx).entry_for_path("ignored-dir").unwrap();
- project.expand_entry(worktree_id, entry.id, cx).unwrap()
- })
- .await
- .unwrap();
-
- project_b.read_with(cx_b, |project, cx| {
- let worktree = project.worktrees().next().unwrap().read(cx);
- assert_eq!(
- worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
- [
- Path::new(".gitignore"),
- Path::new("a.txt"),
- Path::new("b.txt"),
- Path::new("ignored-dir"),
- Path::new("ignored-dir/c.txt"),
- Path::new("ignored-dir/d.txt"),
- ]
- );
- });
-
- // Open the same file as client B and client A.
- let buffer_b = project_b
- .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
- .await
- .unwrap();
-
- buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
-
- project_a.read_with(cx_a, |project, cx| {
- assert!(project.has_open_buffer((worktree_id, "b.txt"), cx))
- });
- let buffer_a = project_a
- .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
- .await
- .unwrap();
-
- let editor_b = window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
-
- // Client A sees client B's selection
- executor.run_until_parked();
-
- buffer_a.read_with(cx_a, |buffer, _| {
- buffer
- .snapshot()
- .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
- .count()
- == 1
- });
-
- // Edit the buffer as client B and see that edit as client A.
- editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx));
- executor.run_until_parked();
-
- buffer_a.read_with(cx_a, |buffer, _| {
- assert_eq!(buffer.text(), "ok, b-contents")
- });
-
- // Client B can invite client C on a project shared by client A.
- active_call_b
- .update(cx_b, |call, cx| {
- call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
- })
- .await
- .unwrap();
-
- let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
- executor.run_until_parked();
- let call = incoming_call_c.borrow().clone().unwrap();
- assert_eq!(call.calling_user.github_login, "user_b");
- let initial_project = call.initial_project.unwrap();
- active_call_c
- .update(cx_c, |call, cx| call.accept_incoming(cx))
- .await
- .unwrap();
- let _project_c = client_c
- .build_remote_project(initial_project.id, cx_c)
- .await;
-
- // Client B closes the editor, and client A sees client B's selections removed.
- cx_b.update(move |_| drop(editor_b));
- executor.run_until_parked();
-
- buffer_a.read_with(cx_a, |buffer, _| {
- buffer
- .snapshot()
- .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
- .count()
- == 0
- });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_on_input_format_from_host_to_guest(
- executor: BackgroundExecutor,
- cx_a: &mut TestAppContext,
- cx_b: &mut TestAppContext,
-) {
- let mut server = TestServer::start(&executor).await;
- let client_a = server.create_client(cx_a, "user_a").await;
- let client_b = server.create_client(cx_b, "user_b").await;
- server
- .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
- .await;
- let active_call_a = cx_a.read(ActiveCall::global);
-
- // Set up a fake language server.
- let mut language = Language::new(
- LanguageConfig {
- name: "Rust".into(),
- path_suffixes: vec!["rs".to_string()],
- ..Default::default()
- },
- Some(tree_sitter_rust::language()),
- );
- let mut fake_language_servers = language
- .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
- capabilities: lsp::ServerCapabilities {
- document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
- first_trigger_character: ":".to_string(),
- more_trigger_character: Some(vec![">".to_string()]),
- }),
- ..Default::default()
- },
- ..Default::default()
- }))
- .await;
- client_a.language_registry().add(Arc::new(language));
-
- client_a
- .fs()
- .insert_tree(
- "/a",
- json!({
- "main.rs": "fn main() { a }",
- "other.rs": "// Test file",
- }),
- )
- .await;
- let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
- let project_id = active_call_a
- .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
- .await
- .unwrap();
- let project_b = client_b.build_remote_project(project_id, cx_b).await;
-
- // Open a file in an editor as the host.
- let buffer_a = project_a
- .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
- .await
- .unwrap();
- let window_a = cx_a.add_empty_window();
- let editor_a = window_a
- .update(cx_a, |_, cx| {
- cx.build_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx))
- })
- .unwrap();
-
- let fake_language_server = fake_language_servers.next().await.unwrap();
- executor.run_until_parked();
-
- // Receive an OnTypeFormatting request as the host's language server.
- // Return some formattings from the host's language server.
- fake_language_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(
- |params, _| async move {
- assert_eq!(
- params.text_document_position.text_document.uri,
- lsp::Url::from_file_path("/a/main.rs").unwrap(),
- );
- assert_eq!(
- params.text_document_position.position,
- lsp::Position::new(0, 14),
- );
-
- Ok(Some(vec![lsp::TextEdit {
- new_text: "~<".to_string(),
- range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
- }]))
- },
- );
-
- // Open the buffer on the guest and see that the formattings worked
- let buffer_b = project_b
- .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
- .await
- .unwrap();
-
- // Type a on type formatting trigger character as the guest.
- editor_a.update(cx_a, |editor, cx| {
- cx.focus(&editor_a);
- editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
- editor.handle_input(">", cx);
- });
-
- executor.run_until_parked();
-
- buffer_b.read_with(cx_b, |buffer, _| {
- assert_eq!(buffer.text(), "fn main() { a>~< }")
- });
-
- // Undo should remove LSP edits first
- editor_a.update(cx_a, |editor, cx| {
- assert_eq!(editor.text(cx), "fn main() { a>~< }");
- editor.undo(&Undo, cx);
- assert_eq!(editor.text(cx), "fn main() { a> }");
- });
- executor.run_until_parked();
-
- buffer_b.read_with(cx_b, |buffer, _| {
- assert_eq!(buffer.text(), "fn main() { a> }")
- });
-
- editor_a.update(cx_a, |editor, cx| {
- assert_eq!(editor.text(cx), "fn main() { a> }");
- editor.undo(&Undo, cx);
- assert_eq!(editor.text(cx), "fn main() { a }");
- });
- executor.run_until_parked();
-
- buffer_b.read_with(cx_b, |buffer, _| {
- assert_eq!(buffer.text(), "fn main() { a }")
- });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_on_input_format_from_guest_to_host(
- executor: BackgroundExecutor,
- cx_a: &mut TestAppContext,
- cx_b: &mut TestAppContext,
-) {
- let mut server = TestServer::start(&executor).await;
- let client_a = server.create_client(cx_a, "user_a").await;
- let client_b = server.create_client(cx_b, "user_b").await;
- server
- .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
- .await;
- let active_call_a = cx_a.read(ActiveCall::global);
-
- // Set up a fake language server.
- let mut language = Language::new(
- LanguageConfig {
- name: "Rust".into(),
- path_suffixes: vec!["rs".to_string()],
- ..Default::default()
- },
- Some(tree_sitter_rust::language()),
- );
- let mut fake_language_servers = language
- .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
- capabilities: lsp::ServerCapabilities {
- document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
- first_trigger_character: ":".to_string(),
- more_trigger_character: Some(vec![">".to_string()]),
- }),
- ..Default::default()
- },
- ..Default::default()
- }))
- .await;
- client_a.language_registry().add(Arc::new(language));
-
- client_a
- .fs()
- .insert_tree(
- "/a",
- json!({
- "main.rs": "fn main() { a }",
- "other.rs": "// Test file",
- }),
- )
- .await;
- let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
- let project_id = active_call_a
- .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
- .await
- .unwrap();
- let project_b = client_b.build_remote_project(project_id, cx_b).await;
-
- // Open a file in an editor as the guest.
- let buffer_b = project_b
- .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
- .await
- .unwrap();
- let window_b = cx_b.add_empty_window();
- let editor_b = window_b.build_view(cx_b, |cx| {
- Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
- });
-
- let fake_language_server = fake_language_servers.next().await.unwrap();
- executor.run_until_parked();
- // Type a on type formatting trigger character as the guest.
- editor_b.update(cx_b, |editor, cx| {
- editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
- editor.handle_input(":", cx);
- cx.focus(&editor_b);
- });
-
- // Receive an OnTypeFormatting request as the host's language server.
- // Return some formattings from the host's language server.
- cx_a.foreground().start_waiting();
- fake_language_server
- .handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
- assert_eq!(
- params.text_document_position.text_document.uri,
- lsp::Url::from_file_path("/a/main.rs").unwrap(),
- );
- assert_eq!(
- params.text_document_position.position,
- lsp::Position::new(0, 14),
- );
-
- Ok(Some(vec![lsp::TextEdit {
- new_text: "~:".to_string(),
- range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
- }]))
- })
- .next()
- .await
- .unwrap();
- cx_a.foreground().finish_waiting();
-
- // Open the buffer on the host and see that the formattings worked
- let buffer_a = project_a
- .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
- .await
- .unwrap();
- executor.run_until_parked();
-
- buffer_a.read_with(cx_a, |buffer, _| {
- assert_eq!(buffer.text(), "fn main() { a:~: }")
- });
-
- // Undo should remove LSP edits first
- editor_b.update(cx_b, |editor, cx| {
- assert_eq!(editor.text(cx), "fn main() { a:~: }");
- editor.undo(&Undo, cx);
- assert_eq!(editor.text(cx), "fn main() { a: }");
- });
- executor.run_until_parked();
-
- buffer_a.read_with(cx_a, |buffer, _| {
- assert_eq!(buffer.text(), "fn main() { a: }")
- });
-
- editor_b.update(cx_b, |editor, cx| {
- assert_eq!(editor.text(cx), "fn main() { a: }");
- editor.undo(&Undo, cx);
- assert_eq!(editor.text(cx), "fn main() { a }");
- });
- executor.run_until_parked();
-
- buffer_a.read_with(cx_a, |buffer, _| {
- assert_eq!(buffer.text(), "fn main() { a }")
- });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_mutual_editor_inlay_hint_cache_update(
- executor: BackgroundExecutor,
- cx_a: &mut TestAppContext,
- cx_b: &mut TestAppContext,
-) {
- let mut server = TestServer::start(&executor).await;
- let client_a = server.create_client(cx_a, "user_a").await;
- let client_b = server.create_client(cx_b, "user_b").await;
- server
- .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
- .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_a.update(|cx| {
- cx.update_global(|store: &mut SettingsStore, cx| {
- store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
- settings.defaults.inlay_hints = Some(InlayHintSettings {
- enabled: true,
- show_type_hints: true,
- show_parameter_hints: false,
- show_other_hints: true,
- })
- });
- });
- });
- cx_b.update(|cx| {
- cx.update_global(|store: &mut SettingsStore, cx| {
- store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
- settings.defaults.inlay_hints = Some(InlayHintSettings {
- enabled: true,
- show_type_hints: true,
- show_parameter_hints: false,
- show_other_hints: true,
- })
- });
- });
- });
-
- let mut language = Language::new(
- LanguageConfig {
- name: "Rust".into(),
- path_suffixes: vec!["rs".to_string()],
- ..Default::default()
- },
- Some(tree_sitter_rust::language()),
- );
- let mut fake_language_servers = language
- .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
- capabilities: lsp::ServerCapabilities {
- inlay_hint_provider: Some(lsp::OneOf::Left(true)),
- ..Default::default()
- },
- ..Default::default()
- }))
- .await;
- let language = Arc::new(language);
- client_a.language_registry().add(Arc::clone(&language));
- client_b.language_registry().add(language);
-
- // Client A opens a project.
- client_a
- .fs()
- .insert_tree(
- "/a",
- json!({
- "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
- "other.rs": "// Test file",
- }),
- )
- .await;
- let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
- active_call_a
- .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
- .await
- .unwrap();
- let project_id = active_call_a
- .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
- .await
- .unwrap();
-
- // Client B joins the project
- let project_b = client_b.build_remote_project(project_id, cx_b).await;
- active_call_b
- .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
- .await
- .unwrap();
-
- let workspace_a = client_a.build_workspace(&project_a, cx_a).root_view(cx_a);
- cx_a.foreground().start_waiting();
-
- // The host opens a rust file.
- let _buffer_a = project_a
- .update(cx_a, |project, cx| {
- project.open_local_buffer("/a/main.rs", cx)
- })
- .await
- .unwrap();
- let fake_language_server = fake_language_servers.next().await.unwrap();
- let editor_a = workspace_a
- .update(cx_a, |workspace, cx| {
- workspace.open_path((worktree_id, "main.rs"), None, true, cx)
- })
- .await
- .unwrap()
- .downcast::<Editor>()
- .unwrap();
-
- // Set up the language server to return an additional inlay hint on each request.
- let edits_made = Arc::new(AtomicUsize::new(0));
- let closure_edits_made = Arc::clone(&edits_made);
- fake_language_server
- .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
- let task_edits_made = Arc::clone(&closure_edits_made);
- async move {
- assert_eq!(
- params.text_document.uri,
- lsp::Url::from_file_path("/a/main.rs").unwrap(),
- );
- let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
- Ok(Some(vec![lsp::InlayHint {
- position: lsp::Position::new(0, edits_made as u32),
- label: lsp::InlayHintLabel::String(edits_made.to_string()),
- kind: None,
- text_edits: None,
- tooltip: None,
- padding_left: None,
- padding_right: None,
- data: None,
- }]))
- }
- })
- .next()
- .await
- .unwrap();
-
- executor.run_until_parked();
-
- let initial_edit = edits_made.load(atomic::Ordering::Acquire);
- editor_a.update(cx_a, |editor, _| {
- assert_eq!(
- vec![initial_edit.to_string()],
- extract_hint_labels(editor),
- "Host should get its first hints when opens an editor"
- );
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(
- inlay_cache.version(),
- 1,
- "Host editor update the cache version after every cache/view change",
- );
- });
- let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
- let editor_b = workspace_b
- .update(cx_b, |workspace, cx| {
- workspace.open_path((worktree_id, "main.rs"), None, true, cx)
- })
- .await
- .unwrap()
- .downcast::<Editor>()
- .unwrap();
-
- executor.run_until_parked();
- editor_b.update(cx_b, |editor, _| {
- assert_eq!(
- vec![initial_edit.to_string()],
- extract_hint_labels(editor),
- "Client should get its first hints when opens an editor"
- );
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(
- inlay_cache.version(),
- 1,
- "Guest editor update the cache version after every cache/view change"
- );
- });
-
- let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
- editor_b.update(cx_b, |editor, cx| {
- editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
- editor.handle_input(":", cx);
- cx.focus(&editor_b);
- });
-
- executor.run_until_parked();
- editor_a.update(cx_a, |editor, _| {
- assert_eq!(
- vec![after_client_edit.to_string()],
- extract_hint_labels(editor),
- );
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version(), 2);
- });
- editor_b.update(cx_b, |editor, _| {
- assert_eq!(
- vec![after_client_edit.to_string()],
- extract_hint_labels(editor),
- );
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version(), 2);
- });
-
- let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
- editor_a.update(cx_a, |editor, cx| {
- editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
- editor.handle_input("a change to increment both buffers' versions", cx);
- cx.focus(&editor_a);
- });
-
- executor.run_until_parked();
- editor_a.update(cx_a, |editor, _| {
- assert_eq!(
- vec![after_host_edit.to_string()],
- extract_hint_labels(editor),
- );
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version(), 3);
- });
- editor_b.update(cx_b, |editor, _| {
- assert_eq!(
- vec![after_host_edit.to_string()],
- extract_hint_labels(editor),
- );
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(inlay_cache.version(), 3);
- });
-
- let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
- fake_language_server
- .request::<lsp::request::InlayHintRefreshRequest>(())
- .await
- .expect("inlay refresh request failed");
-
- executor.run_until_parked();
- editor_a.update(cx_a, |editor, _| {
- assert_eq!(
- vec![after_special_edit_for_refresh.to_string()],
- extract_hint_labels(editor),
- "Host should react to /refresh LSP request"
- );
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(
- inlay_cache.version(),
- 4,
- "Host should accepted all edits and bump its cache version every time"
- );
- });
- editor_b.update(cx_b, |editor, _| {
- assert_eq!(
- vec![after_special_edit_for_refresh.to_string()],
- extract_hint_labels(editor),
- "Guest should get a /refresh LSP request propagated by host"
- );
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(
- inlay_cache.version(),
- 4,
- "Guest should accepted all edits and bump its cache version every time"
- );
- });
-}
-
-#[gpui::test(iterations = 10)]
-async fn test_inlay_hint_refresh_is_forwarded(
- executor: BackgroundExecutor,
- cx_a: &mut TestAppContext,
- cx_b: &mut TestAppContext,
-) {
- let mut server = TestServer::start(&executor).await;
- let client_a = server.create_client(cx_a, "user_a").await;
- let client_b = server.create_client(cx_b, "user_b").await;
- server
- .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
- .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_a.update(|cx| {
- cx.update_global(|store: &mut SettingsStore, cx| {
- store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
- settings.defaults.inlay_hints = Some(InlayHintSettings {
- enabled: false,
- show_type_hints: false,
- show_parameter_hints: false,
- show_other_hints: false,
- })
- });
- });
- });
- cx_b.update(|cx| {
- cx.update_global(|store: &mut SettingsStore, cx| {
- store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
- settings.defaults.inlay_hints = Some(InlayHintSettings {
- enabled: true,
- show_type_hints: true,
- show_parameter_hints: true,
- show_other_hints: true,
- })
- });
- });
- });
-
- let mut language = Language::new(
- LanguageConfig {
- name: "Rust".into(),
- path_suffixes: vec!["rs".to_string()],
- ..Default::default()
- },
- Some(tree_sitter_rust::language()),
- );
- let mut fake_language_servers = language
- .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
- capabilities: lsp::ServerCapabilities {
- inlay_hint_provider: Some(lsp::OneOf::Left(true)),
- ..Default::default()
- },
- ..Default::default()
- }))
- .await;
- let language = Arc::new(language);
- client_a.language_registry().add(Arc::clone(&language));
- client_b.language_registry().add(language);
-
- client_a
- .fs()
- .insert_tree(
- "/a",
- json!({
- "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
- "other.rs": "// Test file",
- }),
- )
- .await;
- let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
- active_call_a
- .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
- .await
- .unwrap();
- let project_id = active_call_a
- .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
- .await
- .unwrap();
-
- let project_b = client_b.build_remote_project(project_id, cx_b).await;
- active_call_b
- .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
- .await
- .unwrap();
-
- 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);
- cx_a.foreground().start_waiting();
- cx_b.foreground().start_waiting();
-
- let editor_a = workspace_a
- .update(cx_a, |workspace, cx| {
- workspace.open_path((worktree_id, "main.rs"), None, true, cx)
- })
- .await
- .unwrap()
- .downcast::<Editor>()
- .unwrap();
-
- let editor_b = workspace_b
- .update(cx_b, |workspace, cx| {
- workspace.open_path((worktree_id, "main.rs"), None, true, cx)
- })
- .await
- .unwrap()
- .downcast::<Editor>()
- .unwrap();
-
- let other_hints = Arc::new(AtomicBool::new(false));
- let fake_language_server = fake_language_servers.next().await.unwrap();
- let closure_other_hints = Arc::clone(&other_hints);
- fake_language_server
- .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
- let task_other_hints = Arc::clone(&closure_other_hints);
- async move {
- assert_eq!(
- params.text_document.uri,
- lsp::Url::from_file_path("/a/main.rs").unwrap(),
- );
- let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
- let character = if other_hints { 0 } else { 2 };
- let label = if other_hints {
- "other hint"
- } else {
- "initial hint"
- };
- Ok(Some(vec![lsp::InlayHint {
- position: lsp::Position::new(0, character),
- label: lsp::InlayHintLabel::String(label.to_string()),
- kind: None,
- text_edits: None,
- tooltip: None,
- padding_left: None,
- padding_right: None,
- data: None,
- }]))
- }
- })
- .next()
- .await
- .unwrap();
- cx_a.foreground().finish_waiting();
- cx_b.foreground().finish_waiting();
-
- executor.run_until_parked();
- editor_a.update(cx_a, |editor, _| {
- assert!(
- extract_hint_labels(editor).is_empty(),
- "Host should get no hints due to them turned off"
- );
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(
- inlay_cache.version(),
- 0,
- "Turned off hints should not generate version updates"
- );
- });
-
- executor.run_until_parked();
- editor_b.update(cx_b, |editor, _| {
- assert_eq!(
- vec!["initial hint".to_string()],
- extract_hint_labels(editor),
- "Client should get its first hints when opens an editor"
- );
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(
- inlay_cache.version(),
- 1,
- "Should update cache verison after first hints"
- );
- });
-
- other_hints.fetch_or(true, atomic::Ordering::Release);
- fake_language_server
- .request::<lsp::request::InlayHintRefreshRequest>(())
- .await
- .expect("inlay refresh request failed");
- executor.run_until_parked();
- editor_a.update(cx_a, |editor, _| {
- assert!(
- extract_hint_labels(editor).is_empty(),
- "Host should get nop hints due to them turned off, even after the /refresh"
- );
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(
- inlay_cache.version(),
- 0,
- "Turned off hints should not generate version updates, again"
- );
- });
-
- executor.run_until_parked();
- editor_b.update(cx_b, |editor, _| {
- assert_eq!(
- vec!["other hint".to_string()],
- extract_hint_labels(editor),
- "Guest should get a /refresh LSP request propagated by host despite host hints are off"
- );
- let inlay_cache = editor.inlay_hint_cache();
- assert_eq!(
- inlay_cache.version(),
- 2,
- "Guest should accepted all edits and bump its cache version every time"
- );
- });
-}
-
-fn extract_hint_labels(editor: &Editor) -> Vec<String> {
- let mut labels = Vec::new();
- for hint in editor.inlay_hint_cache().hints() {
- match hint.label {
- project::InlayHintLabel::String(s) => labels.push(s),
- _ => unreachable!(),
- }
- }
- labels
-}
+//todo(partially ported)
+// use std::{
+// path::Path,
+// sync::{
+// atomic::{self, AtomicBool, AtomicUsize},
+// Arc,
+// },
+// };
+
+// use call::ActiveCall;
+// use editor::{
+// test::editor_test_context::{AssertionContextManager, EditorTestContext},
+// Anchor, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Redo, Rename,
+// ToggleCodeActions, Undo,
+// };
+// use gpui::{BackgroundExecutor, TestAppContext, VisualContext, VisualTestContext};
+// use indoc::indoc;
+// use language::{
+// language_settings::{AllLanguageSettings, InlayHintSettings},
+// tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig,
+// };
+// use rpc::RECEIVE_TIMEOUT;
+// use serde_json::json;
+// use settings::SettingsStore;
+// use text::Point;
+// use workspace::Workspace;
+
+// use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
+
+// #[gpui::test(iterations = 10)]
+// async fn test_host_disconnect(
+// executor: BackgroundExecutor,
+// cx_a: &mut TestAppContext,
+// cx_b: &mut TestAppContext,
+// cx_c: &mut TestAppContext,
+// ) {
+// let mut server = TestServer::start(executor).await;
+// let client_a = server.create_client(cx_a, "user_a").await;
+// let client_b = server.create_client(cx_b, "user_b").await;
+// let client_c = server.create_client(cx_c, "user_c").await;
+// server
+// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
+// .await;
+
+// cx_b.update(editor::init);
+
+// client_a
+// .fs()
+// .insert_tree(
+// "/a",
+// serde_json::json!({
+// "a.txt": "a-contents",
+// "b.txt": "b-contents",
+// }),
+// )
+// .await;
+
+// let active_call_a = cx_a.read(ActiveCall::global);
+// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+
+// let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees().next().unwrap());
+// let project_id = active_call_a
+// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+// .await
+// .unwrap();
+
+// let project_b = client_b.build_remote_project(project_id, cx_b).await;
+// executor.run_until_parked();
+
+// assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
+
+// let workspace_b =
+// cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
+// let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
+
+// let editor_b = workspace_b
+// .update(cx_b, |workspace, cx| {
+// workspace.open_path((worktree_id, "b.txt"), None, true, cx)
+// })
+// .unwrap()
+// .await
+// .unwrap()
+// .downcast::<Editor>()
+// .unwrap();
+
+// //TODO: focus
+// assert!(cx_b.update_view(&editor_b, |editor, cx| editor.is_focused(cx)));
+// editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
+// //todo(is_edited)
+// // assert!(workspace_b.is_edited(cx_b));
+
+// // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
+// server.forbid_connections();
+// server.disconnect_client(client_a.peer_id().unwrap());
+// executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
+
+// project_a.read_with(cx_a, |project, _| project.collaborators().is_empty());
+
+// project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
+
+// project_b.read_with(cx_b, |project, _| project.is_read_only());
+
+// assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
+
+// // Ensure client B's edited state is reset and that the whole window is blurred.
+
+// workspace_b.update(cx_b, |_, cx| {
+// assert_eq!(cx.focused_view_id(), None);
+// });
+// // assert!(!workspace_b.is_edited(cx_b));
+
+// // Ensure client B is not prompted to save edits when closing window after disconnecting.
+// let can_close = workspace_b
+// .update(cx_b, |workspace, cx| workspace.prepare_to_close(true, cx))
+// .await
+// .unwrap();
+// assert!(can_close);
+
+// // Allow client A to reconnect to the server.
+// server.allow_connections();
+// executor.advance_clock(RECEIVE_TIMEOUT);
+
+// // Client B calls client A again after they reconnected.
+// let active_call_b = cx_b.read(ActiveCall::global);
+// active_call_b
+// .update(cx_b, |call, cx| {
+// call.invite(client_a.user_id().unwrap(), None, cx)
+// })
+// .await
+// .unwrap();
+// executor.run_until_parked();
+// active_call_a
+// .update(cx_a, |call, cx| call.accept_incoming(cx))
+// .await
+// .unwrap();
+
+// active_call_a
+// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+// .await
+// .unwrap();
+
+// // Drop client A's connection again. We should still unshare it successfully.
+// server.forbid_connections();
+// server.disconnect_client(client_a.peer_id().unwrap());
+// executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
+
+// project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
+// }
+
+// #[gpui::test]
+// async fn test_newline_above_or_below_does_not_move_guest_cursor(
+// executor: BackgroundExecutor,
+// cx_a: &mut TestAppContext,
+// cx_b: &mut TestAppContext,
+// ) {
+// let mut server = TestServer::start(&executor).await;
+// let client_a = server.create_client(cx_a, "user_a").await;
+// let client_b = server.create_client(cx_b, "user_b").await;
+// server
+// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+// .await;
+// let active_call_a = cx_a.read(ActiveCall::global);
+
+// client_a
+// .fs()
+// .insert_tree("/dir", json!({ "a.txt": "Some text\n" }))
+// .await;
+// let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
+// let project_id = active_call_a
+// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+// .await
+// .unwrap();
+
+// let project_b = client_b.build_remote_project(project_id, cx_b).await;
+
+// // Open a buffer as client A
+// let buffer_a = project_a
+// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
+// .await
+// .unwrap();
+// let window_a = cx_a.add_empty_window();
+// let editor_a =
+// window_a.build_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
+// let mut editor_cx_a = EditorTestContext {
+// cx: cx_a,
+// window: window_a.into(),
+// editor: editor_a,
+// assertion_cx: AssertionContextManager::new(),
+// };
+
+// // Open a buffer as client B
+// let buffer_b = project_b
+// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
+// .await
+// .unwrap();
+// let window_b = cx_b.add_empty_window();
+// let editor_b =
+// window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
+// let mut editor_cx_b = EditorTestContext {
+// cx: cx_b,
+// window: window_b.into(),
+// editor: editor_b,
+// assertion_cx: AssertionContextManager::new(),
+// };
+
+// // Test newline above
+// editor_cx_a.set_selections_state(indoc! {"
+// Some textĖ
+// "});
+// editor_cx_b.set_selections_state(indoc! {"
+// Some textĖ
+// "});
+// editor_cx_a.update_editor(|editor, cx| editor.newline_above(&editor::NewlineAbove, cx));
+// executor.run_until_parked();
+// editor_cx_a.assert_editor_state(indoc! {"
+// Ė
+// Some text
+// "});
+// editor_cx_b.assert_editor_state(indoc! {"
+
+// Some textĖ
+// "});
+
+// // Test newline below
+// editor_cx_a.set_selections_state(indoc! {"
+
+// Some textĖ
+// "});
+// editor_cx_b.set_selections_state(indoc! {"
+
+// Some textĖ
+// "});
+// editor_cx_a.update_editor(|editor, cx| editor.newline_below(&editor::NewlineBelow, cx));
+// executor.run_until_parked();
+// editor_cx_a.assert_editor_state(indoc! {"
+
+// Some text
+// Ė
+// "});
+// editor_cx_b.assert_editor_state(indoc! {"
+
+// Some textĖ
+
+// "});
+// }
+
+// #[gpui::test(iterations = 10)]
+// async fn test_collaborating_with_completion(
+// executor: BackgroundExecutor,
+// cx_a: &mut TestAppContext,
+// cx_b: &mut TestAppContext,
+// ) {
+// let mut server = TestServer::start(&executor).await;
+// let client_a = server.create_client(cx_a, "user_a").await;
+// let client_b = server.create_client(cx_b, "user_b").await;
+// server
+// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+// .await;
+// let active_call_a = cx_a.read(ActiveCall::global);
+
+// // Set up a fake language server.
+// let mut language = Language::new(
+// LanguageConfig {
+// name: "Rust".into(),
+// path_suffixes: vec!["rs".to_string()],
+// ..Default::default()
+// },
+// Some(tree_sitter_rust::language()),
+// );
+// let mut fake_language_servers = language
+// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+// capabilities: lsp::ServerCapabilities {
+// completion_provider: Some(lsp::CompletionOptions {
+// trigger_characters: Some(vec![".".to_string()]),
+// resolve_provider: Some(true),
+// ..Default::default()
+// }),
+// ..Default::default()
+// },
+// ..Default::default()
+// }))
+// .await;
+// client_a.language_registry().add(Arc::new(language));
+
+// client_a
+// .fs()
+// .insert_tree(
+// "/a",
+// json!({
+// "main.rs": "fn main() { a }",
+// "other.rs": "",
+// }),
+// )
+// .await;
+// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+// let project_id = active_call_a
+// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+// .await
+// .unwrap();
+// let project_b = client_b.build_remote_project(project_id, cx_b).await;
+
+// // Open a file in an editor as the guest.
+// let buffer_b = project_b
+// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+// .await
+// .unwrap();
+// let window_b = cx_b.add_empty_window();
+// let editor_b = window_b.build_view(cx_b, |cx| {
+// Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
+// });
+
+// let fake_language_server = fake_language_servers.next().await.unwrap();
+// cx_a.foreground().run_until_parked();
+
+// buffer_b.read_with(cx_b, |buffer, _| {
+// assert!(!buffer.completion_triggers().is_empty())
+// });
+
+// // Type a completion trigger character as the guest.
+// editor_b.update(cx_b, |editor, cx| {
+// editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
+// editor.handle_input(".", cx);
+// cx.focus(&editor_b);
+// });
+
+// // Receive a completion request as the host's language server.
+// // Return some completions from the host's language server.
+// cx_a.foreground().start_waiting();
+// fake_language_server
+// .handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
+// assert_eq!(
+// params.text_document_position.text_document.uri,
+// lsp::Url::from_file_path("/a/main.rs").unwrap(),
+// );
+// assert_eq!(
+// params.text_document_position.position,
+// lsp::Position::new(0, 14),
+// );
+
+// Ok(Some(lsp::CompletionResponse::Array(vec![
+// lsp::CompletionItem {
+// label: "first_method(ā¦)".into(),
+// detail: Some("fn(&mut self, B) -> C".into()),
+// text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+// new_text: "first_method($1)".to_string(),
+// range: lsp::Range::new(
+// lsp::Position::new(0, 14),
+// lsp::Position::new(0, 14),
+// ),
+// })),
+// insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
+// ..Default::default()
+// },
+// lsp::CompletionItem {
+// label: "second_method(ā¦)".into(),
+// detail: Some("fn(&mut self, C) -> D<E>".into()),
+// text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+// new_text: "second_method()".to_string(),
+// range: lsp::Range::new(
+// lsp::Position::new(0, 14),
+// lsp::Position::new(0, 14),
+// ),
+// })),
+// insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
+// ..Default::default()
+// },
+// ])))
+// })
+// .next()
+// .await
+// .unwrap();
+// cx_a.foreground().finish_waiting();
+
+// // Open the buffer on the host.
+// let buffer_a = project_a
+// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+// .await
+// .unwrap();
+// cx_a.foreground().run_until_parked();
+
+// buffer_a.read_with(cx_a, |buffer, _| {
+// assert_eq!(buffer.text(), "fn main() { a. }")
+// });
+
+// // Confirm a completion on the guest.
+
+// editor_b.read_with(cx_b, |editor, _| assert!(editor.context_menu_visible()));
+// editor_b.update(cx_b, |editor, cx| {
+// editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
+// assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
+// });
+
+// // Return a resolved completion from the host's language server.
+// // The resolved completion has an additional text edit.
+// fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
+// |params, _| async move {
+// assert_eq!(params.label, "first_method(ā¦)");
+// Ok(lsp::CompletionItem {
+// label: "first_method(ā¦)".into(),
+// detail: Some("fn(&mut self, B) -> C".into()),
+// text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
+// new_text: "first_method($1)".to_string(),
+// range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
+// })),
+// additional_text_edits: Some(vec![lsp::TextEdit {
+// new_text: "use d::SomeTrait;\n".to_string(),
+// range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
+// }]),
+// insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
+// ..Default::default()
+// })
+// },
+// );
+
+// // The additional edit is applied.
+// cx_a.executor().run_until_parked();
+
+// buffer_a.read_with(cx_a, |buffer, _| {
+// assert_eq!(
+// buffer.text(),
+// "use d::SomeTrait;\nfn main() { a.first_method() }"
+// );
+// });
+
+// buffer_b.read_with(cx_b, |buffer, _| {
+// assert_eq!(
+// buffer.text(),
+// "use d::SomeTrait;\nfn main() { a.first_method() }"
+// );
+// });
+// }
+
+// #[gpui::test(iterations = 10)]
+// async fn test_collaborating_with_code_actions(
+// executor: BackgroundExecutor,
+// cx_a: &mut TestAppContext,
+// cx_b: &mut TestAppContext,
+// ) {
+// let mut server = TestServer::start(&executor).await;
+// let client_a = server.create_client(cx_a, "user_a").await;
+// //
+// let client_b = server.create_client(cx_b, "user_b").await;
+// server
+// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+// .await;
+// let active_call_a = cx_a.read(ActiveCall::global);
+
+// cx_b.update(editor::init);
+
+// // Set up a fake language server.
+// let mut language = Language::new(
+// LanguageConfig {
+// name: "Rust".into(),
+// path_suffixes: vec!["rs".to_string()],
+// ..Default::default()
+// },
+// Some(tree_sitter_rust::language()),
+// );
+// let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
+// client_a.language_registry().add(Arc::new(language));
+
+// client_a
+// .fs()
+// .insert_tree(
+// "/a",
+// json!({
+// "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
+// "other.rs": "pub fn foo() -> usize { 4 }",
+// }),
+// )
+// .await;
+// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+// let project_id = active_call_a
+// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+// .await
+// .unwrap();
+
+// // Join the project as client B.
+// let project_b = client_b.build_remote_project(project_id, cx_b).await;
+// let window_b =
+// cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
+// let workspace_b = window_b.root(cx_b);
+// let editor_b = workspace_b
+// .update(cx_b, |workspace, cx| {
+// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+// })
+// .await
+// .unwrap()
+// .downcast::<Editor>()
+// .unwrap();
+
+// let mut fake_language_server = fake_language_servers.next().await.unwrap();
+// let mut requests = fake_language_server
+// .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
+// assert_eq!(
+// params.text_document.uri,
+// lsp::Url::from_file_path("/a/main.rs").unwrap(),
+// );
+// assert_eq!(params.range.start, lsp::Position::new(0, 0));
+// assert_eq!(params.range.end, lsp::Position::new(0, 0));
+// Ok(None)
+// });
+// executor.advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
+// requests.next().await;
+
+// // Move cursor to a location that contains code actions.
+// editor_b.update(cx_b, |editor, cx| {
+// editor.change_selections(None, cx, |s| {
+// s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
+// });
+// cx.focus(&editor_b);
+// });
+
+// let mut requests = fake_language_server
+// .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
+// assert_eq!(
+// params.text_document.uri,
+// lsp::Url::from_file_path("/a/main.rs").unwrap(),
+// );
+// assert_eq!(params.range.start, lsp::Position::new(1, 31));
+// assert_eq!(params.range.end, lsp::Position::new(1, 31));
+
+// Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
+// lsp::CodeAction {
+// title: "Inline into all callers".to_string(),
+// edit: Some(lsp::WorkspaceEdit {
+// changes: Some(
+// [
+// (
+// lsp::Url::from_file_path("/a/main.rs").unwrap(),
+// vec![lsp::TextEdit::new(
+// lsp::Range::new(
+// lsp::Position::new(1, 22),
+// lsp::Position::new(1, 34),
+// ),
+// "4".to_string(),
+// )],
+// ),
+// (
+// lsp::Url::from_file_path("/a/other.rs").unwrap(),
+// vec![lsp::TextEdit::new(
+// lsp::Range::new(
+// lsp::Position::new(0, 0),
+// lsp::Position::new(0, 27),
+// ),
+// "".to_string(),
+// )],
+// ),
+// ]
+// .into_iter()
+// .collect(),
+// ),
+// ..Default::default()
+// }),
+// data: Some(json!({
+// "codeActionParams": {
+// "range": {
+// "start": {"line": 1, "column": 31},
+// "end": {"line": 1, "column": 31},
+// }
+// }
+// })),
+// ..Default::default()
+// },
+// )]))
+// });
+// executor.advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
+// requests.next().await;
+
+// // Toggle code actions and wait for them to display.
+// editor_b.update(cx_b, |editor, cx| {
+// editor.toggle_code_actions(
+// &ToggleCodeActions {
+// deployed_from_indicator: false,
+// },
+// cx,
+// );
+// });
+// cx_a.foreground().run_until_parked();
+
+// editor_b.read_with(cx_b, |editor, _| assert!(editor.context_menu_visible()));
+
+// fake_language_server.remove_request_handler::<lsp::request::CodeActionRequest>();
+
+// // Confirming the code action will trigger a resolve request.
+// let confirm_action = workspace_b
+// .update(cx_b, |workspace, cx| {
+// Editor::confirm_code_action(workspace, &ConfirmCodeAction { item_ix: Some(0) }, cx)
+// })
+// .unwrap();
+// fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
+// |_, _| async move {
+// Ok(lsp::CodeAction {
+// title: "Inline into all callers".to_string(),
+// edit: Some(lsp::WorkspaceEdit {
+// changes: Some(
+// [
+// (
+// lsp::Url::from_file_path("/a/main.rs").unwrap(),
+// vec![lsp::TextEdit::new(
+// lsp::Range::new(
+// lsp::Position::new(1, 22),
+// lsp::Position::new(1, 34),
+// ),
+// "4".to_string(),
+// )],
+// ),
+// (
+// lsp::Url::from_file_path("/a/other.rs").unwrap(),
+// vec![lsp::TextEdit::new(
+// lsp::Range::new(
+// lsp::Position::new(0, 0),
+// lsp::Position::new(0, 27),
+// ),
+// "".to_string(),
+// )],
+// ),
+// ]
+// .into_iter()
+// .collect(),
+// ),
+// ..Default::default()
+// }),
+// ..Default::default()
+// })
+// },
+// );
+
+// // After the action is confirmed, an editor containing both modified files is opened.
+// confirm_action.await.unwrap();
+
+// let code_action_editor = workspace_b.read_with(cx_b, |workspace, cx| {
+// workspace
+// .active_item(cx)
+// .unwrap()
+// .downcast::<Editor>()
+// .unwrap()
+// });
+// code_action_editor.update(cx_b, |editor, cx| {
+// assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
+// editor.undo(&Undo, cx);
+// assert_eq!(
+// editor.text(cx),
+// "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
+// );
+// editor.redo(&Redo, cx);
+// assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
+// });
+// }
+
+// #[gpui::test(iterations = 10)]
+// async fn test_collaborating_with_renames(
+// executor: BackgroundExecutor,
+// cx_a: &mut TestAppContext,
+// cx_b: &mut TestAppContext,
+// ) {
+// let mut server = TestServer::start(&executor).await;
+// let client_a = server.create_client(cx_a, "user_a").await;
+// let client_b = server.create_client(cx_b, "user_b").await;
+// server
+// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+// .await;
+// let active_call_a = cx_a.read(ActiveCall::global);
+
+// cx_b.update(editor::init);
+
+// // Set up a fake language server.
+// let mut language = Language::new(
+// LanguageConfig {
+// name: "Rust".into(),
+// path_suffixes: vec!["rs".to_string()],
+// ..Default::default()
+// },
+// Some(tree_sitter_rust::language()),
+// );
+// let mut fake_language_servers = language
+// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+// capabilities: lsp::ServerCapabilities {
+// rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
+// prepare_provider: Some(true),
+// work_done_progress_options: Default::default(),
+// })),
+// ..Default::default()
+// },
+// ..Default::default()
+// }))
+// .await;
+// client_a.language_registry().add(Arc::new(language));
+
+// client_a
+// .fs()
+// .insert_tree(
+// "/dir",
+// json!({
+// "one.rs": "const ONE: usize = 1;",
+// "two.rs": "const TWO: usize = one::ONE + one::ONE;"
+// }),
+// )
+// .await;
+// let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
+// let project_id = active_call_a
+// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+// .await
+// .unwrap();
+// let project_b = client_b.build_remote_project(project_id, cx_b).await;
+
+// let window_b =
+// cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
+// let workspace_b = window_b.root(cx_b);
+// let editor_b = workspace_b
+// .update(cx_b, |workspace, cx| {
+// workspace.open_path((worktree_id, "one.rs"), None, true, cx)
+// })
+// .await
+// .unwrap()
+// .downcast::<Editor>()
+// .unwrap();
+// let fake_language_server = fake_language_servers.next().await.unwrap();
+
+// // Move cursor to a location that can be renamed.
+// let prepare_rename = editor_b.update(cx_b, |editor, cx| {
+// editor.change_selections(None, cx, |s| s.select_ranges([7..7]));
+// editor.rename(&Rename, cx).unwrap()
+// });
+
+// fake_language_server
+// .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
+// assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
+// assert_eq!(params.position, lsp::Position::new(0, 7));
+// Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
+// lsp::Position::new(0, 6),
+// lsp::Position::new(0, 9),
+// ))))
+// })
+// .next()
+// .await
+// .unwrap();
+// prepare_rename.await.unwrap();
+// editor_b.update(cx_b, |editor, cx| {
+// use editor::ToOffset;
+// let rename = editor.pending_rename().unwrap();
+// let buffer = editor.buffer().read(cx).snapshot(cx);
+// assert_eq!(
+// rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
+// 6..9
+// );
+// rename.editor.update(cx, |rename_editor, cx| {
+// rename_editor.buffer().update(cx, |rename_buffer, cx| {
+// rename_buffer.edit([(0..3, "THREE")], None, cx);
+// });
+// });
+// });
+
+// let confirm_rename = workspace_b.update(cx_b, |workspace, cx| {
+// Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap()
+// });
+// fake_language_server
+// .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
+// assert_eq!(
+// params.text_document_position.text_document.uri.as_str(),
+// "file:///dir/one.rs"
+// );
+// assert_eq!(
+// params.text_document_position.position,
+// lsp::Position::new(0, 6)
+// );
+// assert_eq!(params.new_name, "THREE");
+// Ok(Some(lsp::WorkspaceEdit {
+// changes: Some(
+// [
+// (
+// lsp::Url::from_file_path("/dir/one.rs").unwrap(),
+// vec![lsp::TextEdit::new(
+// lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
+// "THREE".to_string(),
+// )],
+// ),
+// (
+// lsp::Url::from_file_path("/dir/two.rs").unwrap(),
+// vec![
+// lsp::TextEdit::new(
+// lsp::Range::new(
+// lsp::Position::new(0, 24),
+// lsp::Position::new(0, 27),
+// ),
+// "THREE".to_string(),
+// ),
+// lsp::TextEdit::new(
+// lsp::Range::new(
+// lsp::Position::new(0, 35),
+// lsp::Position::new(0, 38),
+// ),
+// "THREE".to_string(),
+// ),
+// ],
+// ),
+// ]
+// .into_iter()
+// .collect(),
+// ),
+// ..Default::default()
+// }))
+// })
+// .next()
+// .await
+// .unwrap();
+// confirm_rename.await.unwrap();
+
+// let rename_editor = workspace_b.read_with(cx_b, |workspace, cx| {
+// workspace
+// .active_item(cx)
+// .unwrap()
+// .downcast::<Editor>()
+// .unwrap()
+// });
+// rename_editor.update(cx_b, |editor, cx| {
+// assert_eq!(
+// editor.text(cx),
+// "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
+// );
+// editor.undo(&Undo, cx);
+// assert_eq!(
+// editor.text(cx),
+// "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
+// );
+// editor.redo(&Redo, cx);
+// assert_eq!(
+// editor.text(cx),
+// "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
+// );
+// });
+
+// // Ensure temporary rename edits cannot be undone/redone.
+// editor_b.update(cx_b, |editor, cx| {
+// editor.undo(&Undo, cx);
+// assert_eq!(editor.text(cx), "const ONE: usize = 1;");
+// editor.undo(&Undo, cx);
+// assert_eq!(editor.text(cx), "const ONE: usize = 1;");
+// editor.redo(&Redo, cx);
+// assert_eq!(editor.text(cx), "const THREE: usize = 1;");
+// })
+// }
+
+// #[gpui::test(iterations = 10)]
+// async fn test_language_server_statuses(
+// executor: BackgroundExecutor,
+// cx_a: &mut TestAppContext,
+// cx_b: &mut TestAppContext,
+// ) {
+// let mut server = TestServer::start(&executor).await;
+// let client_a = server.create_client(cx_a, "user_a").await;
+// let client_b = server.create_client(cx_b, "user_b").await;
+// server
+// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+// .await;
+// let active_call_a = cx_a.read(ActiveCall::global);
+
+// cx_b.update(editor::init);
+
+// // Set up a fake language server.
+// let mut language = Language::new(
+// LanguageConfig {
+// name: "Rust".into(),
+// path_suffixes: vec!["rs".to_string()],
+// ..Default::default()
+// },
+// Some(tree_sitter_rust::language()),
+// );
+// let mut fake_language_servers = language
+// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+// name: "the-language-server",
+// ..Default::default()
+// }))
+// .await;
+// client_a.language_registry().add(Arc::new(language));
+
+// client_a
+// .fs()
+// .insert_tree(
+// "/dir",
+// json!({
+// "main.rs": "const ONE: usize = 1;",
+// }),
+// )
+// .await;
+// let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
+
+// let _buffer_a = project_a
+// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+// .await
+// .unwrap();
+
+// let fake_language_server = fake_language_servers.next().await.unwrap();
+// fake_language_server.start_progress("the-token").await;
+// fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
+// token: lsp::NumberOrString::String("the-token".to_string()),
+// value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
+// lsp::WorkDoneProgressReport {
+// message: Some("the-message".to_string()),
+// ..Default::default()
+// },
+// )),
+// });
+// executor.run_until_parked();
+
+// project_a.read_with(cx_a, |project, _| {
+// let status = project.language_server_statuses().next().unwrap();
+// assert_eq!(status.name, "the-language-server");
+// assert_eq!(status.pending_work.len(), 1);
+// assert_eq!(
+// status.pending_work["the-token"].message.as_ref().unwrap(),
+// "the-message"
+// );
+// });
+
+// let project_id = active_call_a
+// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+// .await
+// .unwrap();
+// executor.run_until_parked();
+// let project_b = client_b.build_remote_project(project_id, cx_b).await;
+
+// project_b.read_with(cx_b, |project, _| {
+// let status = project.language_server_statuses().next().unwrap();
+// assert_eq!(status.name, "the-language-server");
+// });
+
+// fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
+// token: lsp::NumberOrString::String("the-token".to_string()),
+// value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
+// lsp::WorkDoneProgressReport {
+// message: Some("the-message-2".to_string()),
+// ..Default::default()
+// },
+// )),
+// });
+// executor.run_until_parked();
+
+// project_a.read_with(cx_a, |project, _| {
+// let status = project.language_server_statuses().next().unwrap();
+// assert_eq!(status.name, "the-language-server");
+// assert_eq!(status.pending_work.len(), 1);
+// assert_eq!(
+// status.pending_work["the-token"].message.as_ref().unwrap(),
+// "the-message-2"
+// );
+// });
+
+// project_b.read_with(cx_b, |project, _| {
+// let status = project.language_server_statuses().next().unwrap();
+// assert_eq!(status.name, "the-language-server");
+// assert_eq!(status.pending_work.len(), 1);
+// assert_eq!(
+// status.pending_work["the-token"].message.as_ref().unwrap(),
+// "the-message-2"
+// );
+// });
+// }
+
+// #[gpui::test(iterations = 10)]
+// async fn test_share_project(
+// executor: BackgroundExecutor,
+// cx_a: &mut TestAppContext,
+// cx_b: &mut TestAppContext,
+// cx_c: &mut TestAppContext,
+// ) {
+// let window_b = cx_b.add_empty_window();
+// let mut server = TestServer::start(executor).await;
+// let client_a = server.create_client(cx_a, "user_a").await;
+// let client_b = server.create_client(cx_b, "user_b").await;
+// let client_c = server.create_client(cx_c, "user_c").await;
+// server
+// .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
+// .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);
+
+// client_a
+// .fs()
+// .insert_tree(
+// "/a",
+// json!({
+// ".gitignore": "ignored-dir",
+// "a.txt": "a-contents",
+// "b.txt": "b-contents",
+// "ignored-dir": {
+// "c.txt": "",
+// "d.txt": "",
+// }
+// }),
+// )
+// .await;
+
+// // Invite client B to collaborate on a project
+// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+// active_call_a
+// .update(cx_a, |call, cx| {
+// call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
+// })
+// .await
+// .unwrap();
+
+// // Join that project as client B
+
+// let incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
+// executor.run_until_parked();
+// let call = incoming_call_b.borrow().clone().unwrap();
+// assert_eq!(call.calling_user.github_login, "user_a");
+// let initial_project = call.initial_project.unwrap();
+// active_call_b
+// .update(cx_b, |call, cx| call.accept_incoming(cx))
+// .await
+// .unwrap();
+// let client_b_peer_id = client_b.peer_id().unwrap();
+// let project_b = client_b
+// .build_remote_project(initial_project.id, cx_b)
+// .await;
+
+// let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
+
+// executor.run_until_parked();
+
+// project_a.read_with(cx_a, |project, _| {
+// let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
+// assert_eq!(client_b_collaborator.replica_id, replica_id_b);
+// });
+
+// project_b.read_with(cx_b, |project, cx| {
+// let worktree = project.worktrees().next().unwrap().read(cx);
+// assert_eq!(
+// worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
+// [
+// Path::new(".gitignore"),
+// Path::new("a.txt"),
+// Path::new("b.txt"),
+// Path::new("ignored-dir"),
+// ]
+// );
+// });
+
+// project_b
+// .update(cx_b, |project, cx| {
+// let worktree = project.worktrees().next().unwrap();
+// let entry = worktree.read(cx).entry_for_path("ignored-dir").unwrap();
+// project.expand_entry(worktree_id, entry.id, cx).unwrap()
+// })
+// .await
+// .unwrap();
+
+// project_b.read_with(cx_b, |project, cx| {
+// let worktree = project.worktrees().next().unwrap().read(cx);
+// assert_eq!(
+// worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
+// [
+// Path::new(".gitignore"),
+// Path::new("a.txt"),
+// Path::new("b.txt"),
+// Path::new("ignored-dir"),
+// Path::new("ignored-dir/c.txt"),
+// Path::new("ignored-dir/d.txt"),
+// ]
+// );
+// });
+
+// // Open the same file as client B and client A.
+// let buffer_b = project_b
+// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
+// .await
+// .unwrap();
+
+// buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
+
+// project_a.read_with(cx_a, |project, cx| {
+// assert!(project.has_open_buffer((worktree_id, "b.txt"), cx))
+// });
+// let buffer_a = project_a
+// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
+// .await
+// .unwrap();
+
+// let editor_b = window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
+
+// // Client A sees client B's selection
+// executor.run_until_parked();
+
+// buffer_a.read_with(cx_a, |buffer, _| {
+// buffer
+// .snapshot()
+// .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
+// .count()
+// == 1
+// });
+
+// // Edit the buffer as client B and see that edit as client A.
+// editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx));
+// executor.run_until_parked();
+
+// buffer_a.read_with(cx_a, |buffer, _| {
+// assert_eq!(buffer.text(), "ok, b-contents")
+// });
+
+// // Client B can invite client C on a project shared by client A.
+// active_call_b
+// .update(cx_b, |call, cx| {
+// call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
+// })
+// .await
+// .unwrap();
+
+// let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
+// executor.run_until_parked();
+// let call = incoming_call_c.borrow().clone().unwrap();
+// assert_eq!(call.calling_user.github_login, "user_b");
+// let initial_project = call.initial_project.unwrap();
+// active_call_c
+// .update(cx_c, |call, cx| call.accept_incoming(cx))
+// .await
+// .unwrap();
+// let _project_c = client_c
+// .build_remote_project(initial_project.id, cx_c)
+// .await;
+
+// // Client B closes the editor, and client A sees client B's selections removed.
+// cx_b.update(move |_| drop(editor_b));
+// executor.run_until_parked();
+
+// buffer_a.read_with(cx_a, |buffer, _| {
+// buffer
+// .snapshot()
+// .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
+// .count()
+// == 0
+// });
+// }
+
+// #[gpui::test(iterations = 10)]
+// async fn test_on_input_format_from_host_to_guest(
+// executor: BackgroundExecutor,
+// cx_a: &mut TestAppContext,
+// cx_b: &mut TestAppContext,
+// ) {
+// let mut server = TestServer::start(&executor).await;
+// let client_a = server.create_client(cx_a, "user_a").await;
+// let client_b = server.create_client(cx_b, "user_b").await;
+// server
+// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+// .await;
+// let active_call_a = cx_a.read(ActiveCall::global);
+
+// // Set up a fake language server.
+// let mut language = Language::new(
+// LanguageConfig {
+// name: "Rust".into(),
+// path_suffixes: vec!["rs".to_string()],
+// ..Default::default()
+// },
+// Some(tree_sitter_rust::language()),
+// );
+// let mut fake_language_servers = language
+// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+// capabilities: lsp::ServerCapabilities {
+// document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
+// first_trigger_character: ":".to_string(),
+// more_trigger_character: Some(vec![">".to_string()]),
+// }),
+// ..Default::default()
+// },
+// ..Default::default()
+// }))
+// .await;
+// client_a.language_registry().add(Arc::new(language));
+
+// client_a
+// .fs()
+// .insert_tree(
+// "/a",
+// json!({
+// "main.rs": "fn main() { a }",
+// "other.rs": "// Test file",
+// }),
+// )
+// .await;
+// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+// let project_id = active_call_a
+// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+// .await
+// .unwrap();
+// let project_b = client_b.build_remote_project(project_id, cx_b).await;
+
+// // Open a file in an editor as the host.
+// let buffer_a = project_a
+// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+// .await
+// .unwrap();
+// let window_a = cx_a.add_empty_window();
+// let editor_a = window_a
+// .update(cx_a, |_, cx| {
+// cx.build_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx))
+// })
+// .unwrap();
+
+// let fake_language_server = fake_language_servers.next().await.unwrap();
+// executor.run_until_parked();
+
+// // Receive an OnTypeFormatting request as the host's language server.
+// // Return some formattings from the host's language server.
+// fake_language_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(
+// |params, _| async move {
+// assert_eq!(
+// params.text_document_position.text_document.uri,
+// lsp::Url::from_file_path("/a/main.rs").unwrap(),
+// );
+// assert_eq!(
+// params.text_document_position.position,
+// lsp::Position::new(0, 14),
+// );
+
+// Ok(Some(vec![lsp::TextEdit {
+// new_text: "~<".to_string(),
+// range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
+// }]))
+// },
+// );
+
+// // Open the buffer on the guest and see that the formattings worked
+// let buffer_b = project_b
+// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+// .await
+// .unwrap();
+
+// // Type a on type formatting trigger character as the guest.
+// editor_a.update(cx_a, |editor, cx| {
+// cx.focus(&editor_a);
+// editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
+// editor.handle_input(">", cx);
+// });
+
+// executor.run_until_parked();
+
+// buffer_b.read_with(cx_b, |buffer, _| {
+// assert_eq!(buffer.text(), "fn main() { a>~< }")
+// });
+
+// // Undo should remove LSP edits first
+// editor_a.update(cx_a, |editor, cx| {
+// assert_eq!(editor.text(cx), "fn main() { a>~< }");
+// editor.undo(&Undo, cx);
+// assert_eq!(editor.text(cx), "fn main() { a> }");
+// });
+// executor.run_until_parked();
+
+// buffer_b.read_with(cx_b, |buffer, _| {
+// assert_eq!(buffer.text(), "fn main() { a> }")
+// });
+
+// editor_a.update(cx_a, |editor, cx| {
+// assert_eq!(editor.text(cx), "fn main() { a> }");
+// editor.undo(&Undo, cx);
+// assert_eq!(editor.text(cx), "fn main() { a }");
+// });
+// executor.run_until_parked();
+
+// buffer_b.read_with(cx_b, |buffer, _| {
+// assert_eq!(buffer.text(), "fn main() { a }")
+// });
+// }
+
+// #[gpui::test(iterations = 10)]
+// async fn test_on_input_format_from_guest_to_host(
+// executor: BackgroundExecutor,
+// cx_a: &mut TestAppContext,
+// cx_b: &mut TestAppContext,
+// ) {
+// let mut server = TestServer::start(&executor).await;
+// let client_a = server.create_client(cx_a, "user_a").await;
+// let client_b = server.create_client(cx_b, "user_b").await;
+// server
+// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+// .await;
+// let active_call_a = cx_a.read(ActiveCall::global);
+
+// // Set up a fake language server.
+// let mut language = Language::new(
+// LanguageConfig {
+// name: "Rust".into(),
+// path_suffixes: vec!["rs".to_string()],
+// ..Default::default()
+// },
+// Some(tree_sitter_rust::language()),
+// );
+// let mut fake_language_servers = language
+// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+// capabilities: lsp::ServerCapabilities {
+// document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
+// first_trigger_character: ":".to_string(),
+// more_trigger_character: Some(vec![">".to_string()]),
+// }),
+// ..Default::default()
+// },
+// ..Default::default()
+// }))
+// .await;
+// client_a.language_registry().add(Arc::new(language));
+
+// client_a
+// .fs()
+// .insert_tree(
+// "/a",
+// json!({
+// "main.rs": "fn main() { a }",
+// "other.rs": "// Test file",
+// }),
+// )
+// .await;
+// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+// let project_id = active_call_a
+// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+// .await
+// .unwrap();
+// let project_b = client_b.build_remote_project(project_id, cx_b).await;
+
+// // Open a file in an editor as the guest.
+// let buffer_b = project_b
+// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+// .await
+// .unwrap();
+// let window_b = cx_b.add_empty_window();
+// let editor_b = window_b.build_view(cx_b, |cx| {
+// Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
+// });
+
+// let fake_language_server = fake_language_servers.next().await.unwrap();
+// executor.run_until_parked();
+// // Type a on type formatting trigger character as the guest.
+// editor_b.update(cx_b, |editor, cx| {
+// editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
+// editor.handle_input(":", cx);
+// cx.focus(&editor_b);
+// });
+
+// // Receive an OnTypeFormatting request as the host's language server.
+// // Return some formattings from the host's language server.
+// cx_a.foreground().start_waiting();
+// fake_language_server
+// .handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
+// assert_eq!(
+// params.text_document_position.text_document.uri,
+// lsp::Url::from_file_path("/a/main.rs").unwrap(),
+// );
+// assert_eq!(
+// params.text_document_position.position,
+// lsp::Position::new(0, 14),
+// );
+
+// Ok(Some(vec![lsp::TextEdit {
+// new_text: "~:".to_string(),
+// range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
+// }]))
+// })
+// .next()
+// .await
+// .unwrap();
+// cx_a.foreground().finish_waiting();
+
+// // Open the buffer on the host and see that the formattings worked
+// let buffer_a = project_a
+// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+// .await
+// .unwrap();
+// executor.run_until_parked();
+
+// buffer_a.read_with(cx_a, |buffer, _| {
+// assert_eq!(buffer.text(), "fn main() { a:~: }")
+// });
+
+// // Undo should remove LSP edits first
+// editor_b.update(cx_b, |editor, cx| {
+// assert_eq!(editor.text(cx), "fn main() { a:~: }");
+// editor.undo(&Undo, cx);
+// assert_eq!(editor.text(cx), "fn main() { a: }");
+// });
+// executor.run_until_parked();
+
+// buffer_a.read_with(cx_a, |buffer, _| {
+// assert_eq!(buffer.text(), "fn main() { a: }")
+// });
+
+// editor_b.update(cx_b, |editor, cx| {
+// assert_eq!(editor.text(cx), "fn main() { a: }");
+// editor.undo(&Undo, cx);
+// assert_eq!(editor.text(cx), "fn main() { a }");
+// });
+// executor.run_until_parked();
+
+// buffer_a.read_with(cx_a, |buffer, _| {
+// assert_eq!(buffer.text(), "fn main() { a }")
+// });
+// }
+
+// #[gpui::test(iterations = 10)]
+// async fn test_mutual_editor_inlay_hint_cache_update(
+// executor: BackgroundExecutor,
+// cx_a: &mut TestAppContext,
+// cx_b: &mut TestAppContext,
+// ) {
+// let mut server = TestServer::start(&executor).await;
+// let client_a = server.create_client(cx_a, "user_a").await;
+// let client_b = server.create_client(cx_b, "user_b").await;
+// server
+// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+// .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_a.update(|cx| {
+// cx.update_global(|store: &mut SettingsStore, cx| {
+// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+// settings.defaults.inlay_hints = Some(InlayHintSettings {
+// enabled: true,
+// show_type_hints: true,
+// show_parameter_hints: false,
+// show_other_hints: true,
+// })
+// });
+// });
+// });
+// cx_b.update(|cx| {
+// cx.update_global(|store: &mut SettingsStore, cx| {
+// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+// settings.defaults.inlay_hints = Some(InlayHintSettings {
+// enabled: true,
+// show_type_hints: true,
+// show_parameter_hints: false,
+// show_other_hints: true,
+// })
+// });
+// });
+// });
+
+// let mut language = Language::new(
+// LanguageConfig {
+// name: "Rust".into(),
+// path_suffixes: vec!["rs".to_string()],
+// ..Default::default()
+// },
+// Some(tree_sitter_rust::language()),
+// );
+// let mut fake_language_servers = language
+// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+// capabilities: lsp::ServerCapabilities {
+// inlay_hint_provider: Some(lsp::OneOf::Left(true)),
+// ..Default::default()
+// },
+// ..Default::default()
+// }))
+// .await;
+// let language = Arc::new(language);
+// client_a.language_registry().add(Arc::clone(&language));
+// client_b.language_registry().add(language);
+
+// // Client A opens a project.
+// client_a
+// .fs()
+// .insert_tree(
+// "/a",
+// json!({
+// "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
+// "other.rs": "// Test file",
+// }),
+// )
+// .await;
+// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+// active_call_a
+// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+// .await
+// .unwrap();
+// let project_id = active_call_a
+// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+// .await
+// .unwrap();
+
+// // Client B joins the project
+// let project_b = client_b.build_remote_project(project_id, cx_b).await;
+// active_call_b
+// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+// .await
+// .unwrap();
+
+// let workspace_a = client_a.build_workspace(&project_a, cx_a).root_view(cx_a);
+// cx_a.foreground().start_waiting();
+
+// // The host opens a rust file.
+// let _buffer_a = project_a
+// .update(cx_a, |project, cx| {
+// project.open_local_buffer("/a/main.rs", cx)
+// })
+// .await
+// .unwrap();
+// let fake_language_server = fake_language_servers.next().await.unwrap();
+// let editor_a = workspace_a
+// .update(cx_a, |workspace, cx| {
+// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+// })
+// .await
+// .unwrap()
+// .downcast::<Editor>()
+// .unwrap();
+
+// // Set up the language server to return an additional inlay hint on each request.
+// let edits_made = Arc::new(AtomicUsize::new(0));
+// let closure_edits_made = Arc::clone(&edits_made);
+// fake_language_server
+// .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
+// let task_edits_made = Arc::clone(&closure_edits_made);
+// async move {
+// assert_eq!(
+// params.text_document.uri,
+// lsp::Url::from_file_path("/a/main.rs").unwrap(),
+// );
+// let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
+// Ok(Some(vec![lsp::InlayHint {
+// position: lsp::Position::new(0, edits_made as u32),
+// label: lsp::InlayHintLabel::String(edits_made.to_string()),
+// kind: None,
+// text_edits: None,
+// tooltip: None,
+// padding_left: None,
+// padding_right: None,
+// data: None,
+// }]))
+// }
+// })
+// .next()
+// .await
+// .unwrap();
+
+// executor.run_until_parked();
+
+// let initial_edit = edits_made.load(atomic::Ordering::Acquire);
+// editor_a.update(cx_a, |editor, _| {
+// assert_eq!(
+// vec![initial_edit.to_string()],
+// extract_hint_labels(editor),
+// "Host should get its first hints when opens an editor"
+// );
+// let inlay_cache = editor.inlay_hint_cache();
+// assert_eq!(
+// inlay_cache.version(),
+// 1,
+// "Host editor update the cache version after every cache/view change",
+// );
+// });
+// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
+// let editor_b = workspace_b
+// .update(cx_b, |workspace, cx| {
+// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+// })
+// .await
+// .unwrap()
+// .downcast::<Editor>()
+// .unwrap();
+
+// executor.run_until_parked();
+// editor_b.update(cx_b, |editor, _| {
+// assert_eq!(
+// vec![initial_edit.to_string()],
+// extract_hint_labels(editor),
+// "Client should get its first hints when opens an editor"
+// );
+// let inlay_cache = editor.inlay_hint_cache();
+// assert_eq!(
+// inlay_cache.version(),
+// 1,
+// "Guest editor update the cache version after every cache/view change"
+// );
+// });
+
+// let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
+// editor_b.update(cx_b, |editor, cx| {
+// editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
+// editor.handle_input(":", cx);
+// cx.focus(&editor_b);
+// });
+
+// executor.run_until_parked();
+// editor_a.update(cx_a, |editor, _| {
+// assert_eq!(
+// vec![after_client_edit.to_string()],
+// extract_hint_labels(editor),
+// );
+// let inlay_cache = editor.inlay_hint_cache();
+// assert_eq!(inlay_cache.version(), 2);
+// });
+// editor_b.update(cx_b, |editor, _| {
+// assert_eq!(
+// vec![after_client_edit.to_string()],
+// extract_hint_labels(editor),
+// );
+// let inlay_cache = editor.inlay_hint_cache();
+// assert_eq!(inlay_cache.version(), 2);
+// });
+
+// let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
+// editor_a.update(cx_a, |editor, cx| {
+// editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
+// editor.handle_input("a change to increment both buffers' versions", cx);
+// cx.focus(&editor_a);
+// });
+
+// executor.run_until_parked();
+// editor_a.update(cx_a, |editor, _| {
+// assert_eq!(
+// vec![after_host_edit.to_string()],
+// extract_hint_labels(editor),
+// );
+// let inlay_cache = editor.inlay_hint_cache();
+// assert_eq!(inlay_cache.version(), 3);
+// });
+// editor_b.update(cx_b, |editor, _| {
+// assert_eq!(
+// vec![after_host_edit.to_string()],
+// extract_hint_labels(editor),
+// );
+// let inlay_cache = editor.inlay_hint_cache();
+// assert_eq!(inlay_cache.version(), 3);
+// });
+
+// let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
+// fake_language_server
+// .request::<lsp::request::InlayHintRefreshRequest>(())
+// .await
+// .expect("inlay refresh request failed");
+
+// executor.run_until_parked();
+// editor_a.update(cx_a, |editor, _| {
+// assert_eq!(
+// vec![after_special_edit_for_refresh.to_string()],
+// extract_hint_labels(editor),
+// "Host should react to /refresh LSP request"
+// );
+// let inlay_cache = editor.inlay_hint_cache();
+// assert_eq!(
+// inlay_cache.version(),
+// 4,
+// "Host should accepted all edits and bump its cache version every time"
+// );
+// });
+// editor_b.update(cx_b, |editor, _| {
+// assert_eq!(
+// vec![after_special_edit_for_refresh.to_string()],
+// extract_hint_labels(editor),
+// "Guest should get a /refresh LSP request propagated by host"
+// );
+// let inlay_cache = editor.inlay_hint_cache();
+// assert_eq!(
+// inlay_cache.version(),
+// 4,
+// "Guest should accepted all edits and bump its cache version every time"
+// );
+// });
+// }
+
+// #[gpui::test(iterations = 10)]
+// async fn test_inlay_hint_refresh_is_forwarded(
+// executor: BackgroundExecutor,
+// cx_a: &mut TestAppContext,
+// cx_b: &mut TestAppContext,
+// ) {
+// let mut server = TestServer::start(&executor).await;
+// let client_a = server.create_client(cx_a, "user_a").await;
+// let client_b = server.create_client(cx_b, "user_b").await;
+// server
+// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+// .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_a.update(|cx| {
+// cx.update_global(|store: &mut SettingsStore, cx| {
+// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+// settings.defaults.inlay_hints = Some(InlayHintSettings {
+// enabled: false,
+// show_type_hints: false,
+// show_parameter_hints: false,
+// show_other_hints: false,
+// })
+// });
+// });
+// });
+// cx_b.update(|cx| {
+// cx.update_global(|store: &mut SettingsStore, cx| {
+// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+// settings.defaults.inlay_hints = Some(InlayHintSettings {
+// enabled: true,
+// show_type_hints: true,
+// show_parameter_hints: true,
+// show_other_hints: true,
+// })
+// });
+// });
+// });
+
+// let mut language = Language::new(
+// LanguageConfig {
+// name: "Rust".into(),
+// path_suffixes: vec!["rs".to_string()],
+// ..Default::default()
+// },
+// Some(tree_sitter_rust::language()),
+// );
+// let mut fake_language_servers = language
+// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+// capabilities: lsp::ServerCapabilities {
+// inlay_hint_provider: Some(lsp::OneOf::Left(true)),
+// ..Default::default()
+// },
+// ..Default::default()
+// }))
+// .await;
+// let language = Arc::new(language);
+// client_a.language_registry().add(Arc::clone(&language));
+// client_b.language_registry().add(language);
+
+// client_a
+// .fs()
+// .insert_tree(
+// "/a",
+// json!({
+// "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
+// "other.rs": "// Test file",
+// }),
+// )
+// .await;
+// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+// active_call_a
+// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+// .await
+// .unwrap();
+// let project_id = active_call_a
+// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+// .await
+// .unwrap();
+
+// let project_b = client_b.build_remote_project(project_id, cx_b).await;
+// active_call_b
+// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+// .await
+// .unwrap();
+
+// 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);
+// cx_a.foreground().start_waiting();
+// cx_b.foreground().start_waiting();
+
+// let editor_a = workspace_a
+// .update(cx_a, |workspace, cx| {
+// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+// })
+// .await
+// .unwrap()
+// .downcast::<Editor>()
+// .unwrap();
+
+// let editor_b = workspace_b
+// .update(cx_b, |workspace, cx| {
+// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+// })
+// .await
+// .unwrap()
+// .downcast::<Editor>()
+// .unwrap();
+
+// let other_hints = Arc::new(AtomicBool::new(false));
+// let fake_language_server = fake_language_servers.next().await.unwrap();
+// let closure_other_hints = Arc::clone(&other_hints);
+// fake_language_server
+// .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
+// let task_other_hints = Arc::clone(&closure_other_hints);
+// async move {
+// assert_eq!(
+// params.text_document.uri,
+// lsp::Url::from_file_path("/a/main.rs").unwrap(),
+// );
+// let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
+// let character = if other_hints { 0 } else { 2 };
+// let label = if other_hints {
+// "other hint"
+// } else {
+// "initial hint"
+// };
+// Ok(Some(vec![lsp::InlayHint {
+// position: lsp::Position::new(0, character),
+// label: lsp::InlayHintLabel::String(label.to_string()),
+// kind: None,
+// text_edits: None,
+// tooltip: None,
+// padding_left: None,
+// padding_right: None,
+// data: None,
+// }]))
+// }
+// })
+// .next()
+// .await
+// .unwrap();
+// cx_a.foreground().finish_waiting();
+// cx_b.foreground().finish_waiting();
+
+// executor.run_until_parked();
+// editor_a.update(cx_a, |editor, _| {
+// assert!(
+// extract_hint_labels(editor).is_empty(),
+// "Host should get no hints due to them turned off"
+// );
+// let inlay_cache = editor.inlay_hint_cache();
+// assert_eq!(
+// inlay_cache.version(),
+// 0,
+// "Turned off hints should not generate version updates"
+// );
+// });
+
+// executor.run_until_parked();
+// editor_b.update(cx_b, |editor, _| {
+// assert_eq!(
+// vec!["initial hint".to_string()],
+// extract_hint_labels(editor),
+// "Client should get its first hints when opens an editor"
+// );
+// let inlay_cache = editor.inlay_hint_cache();
+// assert_eq!(
+// inlay_cache.version(),
+// 1,
+// "Should update cache verison after first hints"
+// );
+// });
+
+// other_hints.fetch_or(true, atomic::Ordering::Release);
+// fake_language_server
+// .request::<lsp::request::InlayHintRefreshRequest>(())
+// .await
+// .expect("inlay refresh request failed");
+// executor.run_until_parked();
+// editor_a.update(cx_a, |editor, _| {
+// assert!(
+// extract_hint_labels(editor).is_empty(),
+// "Host should get nop hints due to them turned off, even after the /refresh"
+// );
+// let inlay_cache = editor.inlay_hint_cache();
+// assert_eq!(
+// inlay_cache.version(),
+// 0,
+// "Turned off hints should not generate version updates, again"
+// );
+// });
+
+// executor.run_until_parked();
+// editor_b.update(cx_b, |editor, _| {
+// assert_eq!(
+// vec!["other hint".to_string()],
+// extract_hint_labels(editor),
+// "Guest should get a /refresh LSP request propagated by host despite host hints are off"
+// );
+// let inlay_cache = editor.inlay_hint_cache();
+// assert_eq!(
+// inlay_cache.version(),
+// 2,
+// "Guest should accepted all edits and bump its cache version every time"
+// );
+// });
+// }
+
+// fn extract_hint_labels(editor: &Editor) -> Vec<String> {
+// let mut labels = Vec::new();
+// for hint in editor.inlay_hint_cache().hints() {
+// match hint.label {
+// project::InlayHintLabel::String(s) => labels.push(s),
+// _ => unreachable!(),
+// }
+// }
+// labels
+// }
@@ -36,120 +36,121 @@ use workspace::{
NavigationEntry, ViewId,
};
-#[gpui::test]
-fn test_edit_events(cx: &mut TestAppContext) {
- init_test(cx, |_| {});
+// todo(finish edit tests)
+// #[gpui::test]
+// fn test_edit_events(cx: &mut TestAppContext) {
+// init_test(cx, |_| {});
- let buffer = cx.build_model(|cx| {
- let mut buffer = language::Buffer::new(0, cx.entity_id().as_u64(), "123456");
- buffer.set_group_interval(Duration::from_secs(1));
- buffer
- });
+// let buffer = cx.build_model(|cx| {
+// let mut buffer = language::Buffer::new(0, cx.entity_id().as_u64(), "123456");
+// buffer.set_group_interval(Duration::from_secs(1));
+// buffer
+// });
- let events = Rc::new(RefCell::new(Vec::new()));
- let editor1 = cx.add_window({
- let events = events.clone();
- |cx| {
- let view = cx.view().clone();
- cx.subscribe(&view, move |_, _, event, _| {
- if matches!(event, Event::Edited | Event::BufferEdited) {
- events.borrow_mut().push(("editor1", event.clone()));
- }
- })
- .detach();
- Editor::for_buffer(buffer.clone(), None, cx)
- }
- });
+// let events = Rc::new(RefCell::new(Vec::new()));
+// let editor1 = cx.add_window({
+// let events = events.clone();
+// |cx| {
+// let view = cx.view().clone();
+// cx.subscribe(&view, move |_, _, event, _| {
+// if matches!(event, Event::Edited | Event::BufferEdited) {
+// events.borrow_mut().push(("editor1", event.clone()));
+// }
+// })
+// .detach();
+// Editor::for_buffer(buffer.clone(), None, cx)
+// }
+// });
- let editor2 = cx.add_window({
- let events = events.clone();
- |cx| {
- cx.subscribe(&cx.view().clone(), move |_, _, event, _| {
- if matches!(event, Event::Edited | Event::BufferEdited) {
- events.borrow_mut().push(("editor2", event.clone()));
- }
- })
- .detach();
- Editor::for_buffer(buffer.clone(), None, cx)
- }
- });
+// let editor2 = cx.add_window({
+// let events = events.clone();
+// |cx| {
+// cx.subscribe(&cx.view().clone(), move |_, _, event, _| {
+// if matches!(event, Event::Edited | Event::BufferEdited) {
+// events.borrow_mut().push(("editor2", event.clone()));
+// }
+// })
+// .detach();
+// Editor::for_buffer(buffer.clone(), None, cx)
+// }
+// });
- assert_eq!(mem::take(&mut *events.borrow_mut()), []);
+// assert_eq!(mem::take(&mut *events.borrow_mut()), []);
- // Mutating editor 1 will emit an `Edited` event only for that editor.
- editor1.update(cx, |editor, cx| editor.insert("X", cx));
- assert_eq!(
- mem::take(&mut *events.borrow_mut()),
- [
- ("editor1", Event::Edited),
- ("editor1", Event::BufferEdited),
- ("editor2", Event::BufferEdited),
- ]
- );
+// // Mutating editor 1 will emit an `Edited` event only for that editor.
+// editor1.update(cx, |editor, cx| editor.insert("X", cx));
+// assert_eq!(
+// mem::take(&mut *events.borrow_mut()),
+// [
+// ("editor1", Event::Edited),
+// ("editor1", Event::BufferEdited),
+// ("editor2", Event::BufferEdited),
+// ]
+// );
- // Mutating editor 2 will emit an `Edited` event only for that editor.
- editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
- assert_eq!(
- mem::take(&mut *events.borrow_mut()),
- [
- ("editor2", Event::Edited),
- ("editor1", Event::BufferEdited),
- ("editor2", Event::BufferEdited),
- ]
- );
+// // Mutating editor 2 will emit an `Edited` event only for that editor.
+// editor2.update(cx, |editor, cx| editor.delete(&Delete, cx));
+// assert_eq!(
+// mem::take(&mut *events.borrow_mut()),
+// [
+// ("editor2", Event::Edited),
+// ("editor1", Event::BufferEdited),
+// ("editor2", Event::BufferEdited),
+// ]
+// );
- // Undoing on editor 1 will emit an `Edited` event only for that editor.
- editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
- assert_eq!(
- mem::take(&mut *events.borrow_mut()),
- [
- ("editor1", Event::Edited),
- ("editor1", Event::BufferEdited),
- ("editor2", Event::BufferEdited),
- ]
- );
+// // Undoing on editor 1 will emit an `Edited` event only for that editor.
+// editor1.update(cx, |editor, cx| editor.undo(&Undo, cx));
+// assert_eq!(
+// mem::take(&mut *events.borrow_mut()),
+// [
+// ("editor1", Event::Edited),
+// ("editor1", Event::BufferEdited),
+// ("editor2", Event::BufferEdited),
+// ]
+// );
- // Redoing on editor 1 will emit an `Edited` event only for that editor.
- editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
- assert_eq!(
- mem::take(&mut *events.borrow_mut()),
- [
- ("editor1", Event::Edited),
- ("editor1", Event::BufferEdited),
- ("editor2", Event::BufferEdited),
- ]
- );
+// // Redoing on editor 1 will emit an `Edited` event only for that editor.
+// editor1.update(cx, |editor, cx| editor.redo(&Redo, cx));
+// assert_eq!(
+// mem::take(&mut *events.borrow_mut()),
+// [
+// ("editor1", Event::Edited),
+// ("editor1", Event::BufferEdited),
+// ("editor2", Event::BufferEdited),
+// ]
+// );
- // Undoing on editor 2 will emit an `Edited` event only for that editor.
- editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
- assert_eq!(
- mem::take(&mut *events.borrow_mut()),
- [
- ("editor2", Event::Edited),
- ("editor1", Event::BufferEdited),
- ("editor2", Event::BufferEdited),
- ]
- );
+// // Undoing on editor 2 will emit an `Edited` event only for that editor.
+// editor2.update(cx, |editor, cx| editor.undo(&Undo, cx));
+// assert_eq!(
+// mem::take(&mut *events.borrow_mut()),
+// [
+// ("editor2", Event::Edited),
+// ("editor1", Event::BufferEdited),
+// ("editor2", Event::BufferEdited),
+// ]
+// );
- // Redoing on editor 2 will emit an `Edited` event only for that editor.
- editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
- assert_eq!(
- mem::take(&mut *events.borrow_mut()),
- [
- ("editor2", Event::Edited),
- ("editor1", Event::BufferEdited),
- ("editor2", Event::BufferEdited),
- ]
- );
+// // Redoing on editor 2 will emit an `Edited` event only for that editor.
+// editor2.update(cx, |editor, cx| editor.redo(&Redo, cx));
+// assert_eq!(
+// mem::take(&mut *events.borrow_mut()),
+// [
+// ("editor2", Event::Edited),
+// ("editor1", Event::BufferEdited),
+// ("editor2", Event::BufferEdited),
+// ]
+// );
- // No event is emitted when the mutation is a no-op.
- editor2.update(cx, |editor, cx| {
- editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
+// // No event is emitted when the mutation is a no-op.
+// editor2.update(cx, |editor, cx| {
+// editor.change_selections(None, cx, |s| s.select_ranges([0..0]));
- editor.backspace(&Backspace, cx);
- });
- assert_eq!(mem::take(&mut *events.borrow_mut()), []);
-}
+// editor.backspace(&Backspace, cx);
+// });
+// assert_eq!(mem::take(&mut *events.borrow_mut()), []);
+// }
#[gpui::test]
fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
@@ -513,123 +514,124 @@ fn test_clone(cx: &mut TestAppContext) {
);
}
-#[gpui::test]
-async fn test_navigation_history(cx: &mut TestAppContext) {
- init_test(cx, |_| {});
-
- use workspace::item::Item;
+//todo!(editor navigate)
+// #[gpui::test]
+// async fn test_navigation_history(cx: &mut TestAppContext) {
+// init_test(cx, |_| {});
- let fs = FakeFs::new(cx.executor());
- let project = Project::test(fs, [], cx).await;
- let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
- let pane = workspace
- .update(cx, |workspace, _| workspace.active_pane().clone())
- .unwrap();
+// use workspace::item::Item;
- workspace.update(cx, |v, cx| {
- cx.build_view(|cx| {
- let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
- let mut editor = build_editor(buffer.clone(), cx);
- let handle = cx.view();
- editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
+// let fs = FakeFs::new(cx.executor());
+// let project = Project::test(fs, [], cx).await;
+// let workspace = cx.add_window(|cx| Workspace::test_new(project, cx));
+// let pane = workspace
+// .update(cx, |workspace, _| workspace.active_pane().clone())
+// .unwrap();
- fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
- editor.nav_history.as_mut().unwrap().pop_backward(cx)
- }
+// workspace.update(cx, |v, cx| {
+// cx.build_view(|cx| {
+// let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
+// let mut editor = build_editor(buffer.clone(), cx);
+// let handle = cx.view();
+// editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
+
+// fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option<NavigationEntry> {
+// editor.nav_history.as_mut().unwrap().pop_backward(cx)
+// }
+
+// // Move the cursor a small distance.
+// // Nothing is added to the navigation history.
+// editor.change_selections(None, cx, |s| {
+// s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
+// });
+// editor.change_selections(None, cx, |s| {
+// s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
+// });
+// assert!(pop_history(&mut editor, cx).is_none());
- // Move the cursor a small distance.
- // Nothing is added to the navigation history.
- editor.change_selections(None, cx, |s| {
- s.select_display_ranges([DisplayPoint::new(1, 0)..DisplayPoint::new(1, 0)])
- });
- editor.change_selections(None, cx, |s| {
- s.select_display_ranges([DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)])
- });
- assert!(pop_history(&mut editor, cx).is_none());
-
- // Move the cursor a large distance.
- // The history can jump back to the previous position.
- editor.change_selections(None, cx, |s| {
- s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)])
- });
- let nav_entry = pop_history(&mut editor, cx).unwrap();
- editor.navigate(nav_entry.data.unwrap(), cx);
- assert_eq!(nav_entry.item.id(), cx.entity_id());
- assert_eq!(
- editor.selections.display_ranges(cx),
- &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
- );
- assert!(pop_history(&mut editor, cx).is_none());
+// // Move the cursor a large distance.
+// // The history can jump back to the previous position.
+// editor.change_selections(None, cx, |s| {
+// s.select_display_ranges([DisplayPoint::new(13, 0)..DisplayPoint::new(13, 3)])
+// });
+// let nav_entry = pop_history(&mut editor, cx).unwrap();
+// editor.navigate(nav_entry.data.unwrap(), cx);
+// assert_eq!(nav_entry.item.id(), cx.entity_id());
+// assert_eq!(
+// editor.selections.display_ranges(cx),
+// &[DisplayPoint::new(3, 0)..DisplayPoint::new(3, 0)]
+// );
+// assert!(pop_history(&mut editor, cx).is_none());
- // Move the cursor a small distance via the mouse.
- // Nothing is added to the navigation history.
- editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx);
- editor.end_selection(cx);
- assert_eq!(
- editor.selections.display_ranges(cx),
- &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
- );
- assert!(pop_history(&mut editor, cx).is_none());
+// // Move the cursor a small distance via the mouse.
+// // Nothing is added to the navigation history.
+// editor.begin_selection(DisplayPoint::new(5, 0), false, 1, cx);
+// editor.end_selection(cx);
+// assert_eq!(
+// editor.selections.display_ranges(cx),
+// &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
+// );
+// assert!(pop_history(&mut editor, cx).is_none());
- // Move the cursor a large distance via the mouse.
- // The history can jump back to the previous position.
- editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx);
- editor.end_selection(cx);
- assert_eq!(
- editor.selections.display_ranges(cx),
- &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)]
- );
- let nav_entry = pop_history(&mut editor, cx).unwrap();
- editor.navigate(nav_entry.data.unwrap(), cx);
- assert_eq!(nav_entry.item.id(), cx.entity_id());
- assert_eq!(
- editor.selections.display_ranges(cx),
- &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
- );
- assert!(pop_history(&mut editor, cx).is_none());
-
- // Set scroll position to check later
- editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
- let original_scroll_position = editor.scroll_manager.anchor();
-
- // Jump to the end of the document and adjust scroll
- editor.move_to_end(&MoveToEnd, cx);
- editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
- assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
-
- let nav_entry = pop_history(&mut editor, cx).unwrap();
- editor.navigate(nav_entry.data.unwrap(), cx);
- assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
-
- // Ensure we don't panic when navigation data contains invalid anchors *and* points.
- let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
- invalid_anchor.text_anchor.buffer_id = Some(999);
- let invalid_point = Point::new(9999, 0);
- editor.navigate(
- Box::new(NavigationData {
- cursor_anchor: invalid_anchor,
- cursor_position: invalid_point,
- scroll_anchor: ScrollAnchor {
- anchor: invalid_anchor,
- offset: Default::default(),
- },
- scroll_top_row: invalid_point.row,
- }),
- cx,
- );
- assert_eq!(
- editor.selections.display_ranges(cx),
- &[editor.max_point(cx)..editor.max_point(cx)]
- );
- assert_eq!(
- editor.scroll_position(cx),
- gpui::Point::new(0., editor.max_point(cx).row() as f32)
- );
+// // Move the cursor a large distance via the mouse.
+// // The history can jump back to the previous position.
+// editor.begin_selection(DisplayPoint::new(15, 0), false, 1, cx);
+// editor.end_selection(cx);
+// assert_eq!(
+// editor.selections.display_ranges(cx),
+// &[DisplayPoint::new(15, 0)..DisplayPoint::new(15, 0)]
+// );
+// let nav_entry = pop_history(&mut editor, cx).unwrap();
+// editor.navigate(nav_entry.data.unwrap(), cx);
+// assert_eq!(nav_entry.item.id(), cx.entity_id());
+// assert_eq!(
+// editor.selections.display_ranges(cx),
+// &[DisplayPoint::new(5, 0)..DisplayPoint::new(5, 0)]
+// );
+// assert!(pop_history(&mut editor, cx).is_none());
+
+// // Set scroll position to check later
+// editor.set_scroll_position(gpui::Point::<f32>::new(5.5, 5.5), cx);
+// let original_scroll_position = editor.scroll_manager.anchor();
+
+// // Jump to the end of the document and adjust scroll
+// editor.move_to_end(&MoveToEnd, cx);
+// editor.set_scroll_position(gpui::Point::<f32>::new(-2.5, -0.5), cx);
+// assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
+
+// let nav_entry = pop_history(&mut editor, cx).unwrap();
+// editor.navigate(nav_entry.data.unwrap(), cx);
+// assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
+
+// // Ensure we don't panic when navigation data contains invalid anchors *and* points.
+// let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
+// invalid_anchor.text_anchor.buffer_id = Some(999);
+// let invalid_point = Point::new(9999, 0);
+// editor.navigate(
+// Box::new(NavigationData {
+// cursor_anchor: invalid_anchor,
+// cursor_position: invalid_point,
+// scroll_anchor: ScrollAnchor {
+// anchor: invalid_anchor,
+// offset: Default::default(),
+// },
+// scroll_top_row: invalid_point.row,
+// }),
+// cx,
+// );
+// assert_eq!(
+// editor.selections.display_ranges(cx),
+// &[editor.max_point(cx)..editor.max_point(cx)]
+// );
+// assert_eq!(
+// editor.scroll_position(cx),
+// gpui::Point::new(0., editor.max_point(cx).row() as f32)
+// );
- editor
- })
- });
-}
+// editor
+// })
+// });
+// }
#[gpui::test]
fn test_cancel(cx: &mut TestAppContext) {
@@ -956,55 +958,56 @@ fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
});
}
-#[gpui::test]
-fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
- init_test(cx, |_| {});
+//todo!(finish editor tests)
+// #[gpui::test]
+// fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
+// init_test(cx, |_| {});
- let view = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("āāāāā\nabcd\nαβγ\nabcd\nāāāāā\n", cx);
- build_editor(buffer.clone(), cx)
- });
- view.update(cx, |view, cx| {
- view.change_selections(None, cx, |s| {
- s.select_display_ranges([empty_range(0, "āāāāā".len())]);
- });
- view.move_down(&MoveDown, cx);
- assert_eq!(
- view.selections.display_ranges(cx),
- &[empty_range(1, "abcd".len())]
- );
+// let view = cx.add_window(|cx| {
+// let buffer = MultiBuffer::build_simple("āāāāā\nabcd\nαβγ\nabcd\nāāāāā\n", cx);
+// build_editor(buffer.clone(), cx)
+// });
+// view.update(cx, |view, cx| {
+// view.change_selections(None, cx, |s| {
+// s.select_display_ranges([empty_range(0, "āāāāā".len())]);
+// });
+// view.move_down(&MoveDown, cx);
+// assert_eq!(
+// view.selections.display_ranges(cx),
+// &[empty_range(1, "abcd".len())]
+// );
- view.move_down(&MoveDown, cx);
- assert_eq!(
- view.selections.display_ranges(cx),
- &[empty_range(2, "αβγ".len())]
- );
+// view.move_down(&MoveDown, cx);
+// assert_eq!(
+// view.selections.display_ranges(cx),
+// &[empty_range(2, "αβγ".len())]
+// );
- view.move_down(&MoveDown, cx);
- assert_eq!(
- view.selections.display_ranges(cx),
- &[empty_range(3, "abcd".len())]
- );
+// view.move_down(&MoveDown, cx);
+// assert_eq!(
+// view.selections.display_ranges(cx),
+// &[empty_range(3, "abcd".len())]
+// );
- view.move_down(&MoveDown, cx);
- assert_eq!(
- view.selections.display_ranges(cx),
- &[empty_range(4, "āāāāā".len())]
- );
+// view.move_down(&MoveDown, cx);
+// assert_eq!(
+// view.selections.display_ranges(cx),
+// &[empty_range(4, "āāāāā".len())]
+// );
- view.move_up(&MoveUp, cx);
- assert_eq!(
- view.selections.display_ranges(cx),
- &[empty_range(3, "abcd".len())]
- );
+// view.move_up(&MoveUp, cx);
+// assert_eq!(
+// view.selections.display_ranges(cx),
+// &[empty_range(3, "abcd".len())]
+// );
- view.move_up(&MoveUp, cx);
- assert_eq!(
- view.selections.display_ranges(cx),
- &[empty_range(2, "αβγ".len())]
- );
- });
-}
+// view.move_up(&MoveUp, cx);
+// assert_eq!(
+// view.selections.display_ranges(cx),
+// &[empty_range(2, "αβγ".len())]
+// );
+// });
+// }
#[gpui::test]
fn test_beginning_end_of_line(cx: &mut TestAppContext) {
@@ -1221,63 +1224,64 @@ fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
});
}
-#[gpui::test]
-fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
- init_test(cx, |_| {});
+//todo!(finish editor tests)
+// #[gpui::test]
+// fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
+// init_test(cx, |_| {});
- let view = cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
- build_editor(buffer, cx)
- });
+// let view = cx.add_window(|cx| {
+// let buffer = MultiBuffer::build_simple("use one::{\n two::three::four::five\n};", cx);
+// build_editor(buffer, cx)
+// });
- view.update(cx, |view, cx| {
- view.set_wrap_width(Some(140.0.into()), cx);
- assert_eq!(
- view.display_text(cx),
- "use one::{\n two::three::\n four::five\n};"
- );
+// view.update(cx, |view, cx| {
+// view.set_wrap_width(Some(140.0.into()), cx);
+// assert_eq!(
+// view.display_text(cx),
+// "use one::{\n two::three::\n four::five\n};"
+// );
- view.change_selections(None, cx, |s| {
- s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]);
- });
+// view.change_selections(None, cx, |s| {
+// s.select_display_ranges([DisplayPoint::new(1, 7)..DisplayPoint::new(1, 7)]);
+// });
- view.move_to_next_word_end(&MoveToNextWordEnd, cx);
- assert_eq!(
- view.selections.display_ranges(cx),
- &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)]
- );
+// view.move_to_next_word_end(&MoveToNextWordEnd, cx);
+// assert_eq!(
+// view.selections.display_ranges(cx),
+// &[DisplayPoint::new(1, 9)..DisplayPoint::new(1, 9)]
+// );
- view.move_to_next_word_end(&MoveToNextWordEnd, cx);
- assert_eq!(
- view.selections.display_ranges(cx),
- &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
- );
+// view.move_to_next_word_end(&MoveToNextWordEnd, cx);
+// assert_eq!(
+// view.selections.display_ranges(cx),
+// &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
+// );
- view.move_to_next_word_end(&MoveToNextWordEnd, cx);
- assert_eq!(
- view.selections.display_ranges(cx),
- &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
- );
+// view.move_to_next_word_end(&MoveToNextWordEnd, cx);
+// assert_eq!(
+// view.selections.display_ranges(cx),
+// &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
+// );
- view.move_to_next_word_end(&MoveToNextWordEnd, cx);
- assert_eq!(
- view.selections.display_ranges(cx),
- &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)]
- );
+// view.move_to_next_word_end(&MoveToNextWordEnd, cx);
+// assert_eq!(
+// view.selections.display_ranges(cx),
+// &[DisplayPoint::new(2, 8)..DisplayPoint::new(2, 8)]
+// );
- view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
- assert_eq!(
- view.selections.display_ranges(cx),
- &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
- );
+// view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
+// assert_eq!(
+// view.selections.display_ranges(cx),
+// &[DisplayPoint::new(2, 4)..DisplayPoint::new(2, 4)]
+// );
- view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
- assert_eq!(
- view.selections.display_ranges(cx),
- &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
- );
- });
-}
+// view.move_to_previous_word_start(&MoveToPreviousWordStart, cx);
+// assert_eq!(
+// view.selections.display_ranges(cx),
+// &[DisplayPoint::new(1, 14)..DisplayPoint::new(1, 14)]
+// );
+// });
+// }
//todo!(simulate_resize)
// #[gpui::test]
@@ -2488,136 +2492,137 @@ fn test_delete_line(cx: &mut TestAppContext) {
});
}
-#[gpui::test]
-fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
- init_test(cx, |_| {});
-
- cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
- let mut editor = build_editor(buffer.clone(), cx);
- let buffer = buffer.read(cx).as_singleton().unwrap();
-
- assert_eq!(
- editor.selections.ranges::<Point>(cx),
- &[Point::new(0, 0)..Point::new(0, 0)]
- );
-
- // When on single line, replace newline at end by space
- editor.join_lines(&JoinLines, cx);
- assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
- assert_eq!(
- editor.selections.ranges::<Point>(cx),
- &[Point::new(0, 3)..Point::new(0, 3)]
- );
+//todo!(select_anchor_ranges)
+// #[gpui::test]
+// fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
+// init_test(cx, |_| {});
- // When multiple lines are selected, remove newlines that are spanned by the selection
- editor.change_selections(None, cx, |s| {
- s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
- });
- editor.join_lines(&JoinLines, cx);
- assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
- assert_eq!(
- editor.selections.ranges::<Point>(cx),
- &[Point::new(0, 11)..Point::new(0, 11)]
- );
+// cx.add_window(|cx| {
+// let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
+// let mut editor = build_editor(buffer.clone(), cx);
+// let buffer = buffer.read(cx).as_singleton().unwrap();
- // Undo should be transactional
- editor.undo(&Undo, cx);
- assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
- assert_eq!(
- editor.selections.ranges::<Point>(cx),
- &[Point::new(0, 5)..Point::new(2, 2)]
- );
+// assert_eq!(
+// editor.selections.ranges::<Point>(cx),
+// &[Point::new(0, 0)..Point::new(0, 0)]
+// );
- // When joining an empty line don't insert a space
- editor.change_selections(None, cx, |s| {
- s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
- });
- editor.join_lines(&JoinLines, cx);
- assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
- assert_eq!(
- editor.selections.ranges::<Point>(cx),
- [Point::new(2, 3)..Point::new(2, 3)]
- );
+// // When on single line, replace newline at end by space
+// editor.join_lines(&JoinLines, cx);
+// assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
+// assert_eq!(
+// editor.selections.ranges::<Point>(cx),
+// &[Point::new(0, 3)..Point::new(0, 3)]
+// );
- // We can remove trailing newlines
- editor.join_lines(&JoinLines, cx);
- assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
- assert_eq!(
- editor.selections.ranges::<Point>(cx),
- [Point::new(2, 3)..Point::new(2, 3)]
- );
+// // When multiple lines are selected, remove newlines that are spanned by the selection
+// editor.change_selections(None, cx, |s| {
+// s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
+// });
+// editor.join_lines(&JoinLines, cx);
+// assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
+// assert_eq!(
+// editor.selections.ranges::<Point>(cx),
+// &[Point::new(0, 11)..Point::new(0, 11)]
+// );
- // We don't blow up on the last line
- editor.join_lines(&JoinLines, cx);
- assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
- assert_eq!(
- editor.selections.ranges::<Point>(cx),
- [Point::new(2, 3)..Point::new(2, 3)]
- );
+// // Undo should be transactional
+// editor.undo(&Undo, cx);
+// assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
+// assert_eq!(
+// editor.selections.ranges::<Point>(cx),
+// &[Point::new(0, 5)..Point::new(2, 2)]
+// );
- // reset to test indentation
- editor.buffer.update(cx, |buffer, cx| {
- buffer.edit(
- [
- (Point::new(1, 0)..Point::new(1, 2), " "),
- (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
- ],
- None,
- cx,
- )
- });
+// // When joining an empty line don't insert a space
+// editor.change_selections(None, cx, |s| {
+// s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
+// });
+// editor.join_lines(&JoinLines, cx);
+// assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
+// assert_eq!(
+// editor.selections.ranges::<Point>(cx),
+// [Point::new(2, 3)..Point::new(2, 3)]
+// );
- // We remove any leading spaces
- assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
- editor.change_selections(None, cx, |s| {
- s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
- });
- editor.join_lines(&JoinLines, cx);
- assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
+// // We can remove trailing newlines
+// editor.join_lines(&JoinLines, cx);
+// assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
+// assert_eq!(
+// editor.selections.ranges::<Point>(cx),
+// [Point::new(2, 3)..Point::new(2, 3)]
+// );
- // We don't insert a space for a line containing only spaces
- editor.join_lines(&JoinLines, cx);
- assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
+// // We don't blow up on the last line
+// editor.join_lines(&JoinLines, cx);
+// assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
+// assert_eq!(
+// editor.selections.ranges::<Point>(cx),
+// [Point::new(2, 3)..Point::new(2, 3)]
+// );
- // We ignore any leading tabs
- editor.join_lines(&JoinLines, cx);
- assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
+// // reset to test indentation
+// editor.buffer.update(cx, |buffer, cx| {
+// buffer.edit(
+// [
+// (Point::new(1, 0)..Point::new(1, 2), " "),
+// (Point::new(2, 0)..Point::new(2, 3), " \n\td"),
+// ],
+// None,
+// cx,
+// )
+// });
- editor
- });
-}
+// // We remove any leading spaces
+// assert_eq!(buffer.read(cx).text(), "aaa bbb\n c\n \n\td");
+// editor.change_selections(None, cx, |s| {
+// s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
+// });
+// editor.join_lines(&JoinLines, cx);
+// assert_eq!(buffer.read(cx).text(), "aaa bbb c\n \n\td");
-#[gpui::test]
-fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
- init_test(cx, |_| {});
+// // We don't insert a space for a line containing only spaces
+// editor.join_lines(&JoinLines, cx);
+// assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
- cx.add_window(|cx| {
- let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
- let mut editor = build_editor(buffer.clone(), cx);
- let buffer = buffer.read(cx).as_singleton().unwrap();
+// // We ignore any leading tabs
+// editor.join_lines(&JoinLines, cx);
+// assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
- editor.change_selections(None, cx, |s| {
- s.select_ranges([
- Point::new(0, 2)..Point::new(1, 1),
- Point::new(1, 2)..Point::new(1, 2),
- Point::new(3, 1)..Point::new(3, 2),
- ])
- });
+// editor
+// });
+// }
- editor.join_lines(&JoinLines, cx);
- assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
+// #[gpui::test]
+// fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
+// init_test(cx, |_| {});
- assert_eq!(
- editor.selections.ranges::<Point>(cx),
- [
- Point::new(0, 7)..Point::new(0, 7),
- Point::new(1, 3)..Point::new(1, 3)
- ]
- );
- editor
- });
-}
+// cx.add_window(|cx| {
+// let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
+// let mut editor = build_editor(buffer.clone(), cx);
+// let buffer = buffer.read(cx).as_singleton().unwrap();
+
+// editor.change_selections(None, cx, |s| {
+// s.select_ranges([
+// Point::new(0, 2)..Point::new(1, 1),
+// Point::new(1, 2)..Point::new(1, 2),
+// Point::new(3, 1)..Point::new(3, 2),
+// ])
+// });
+
+// editor.join_lines(&JoinLines, cx);
+// assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
+
+// assert_eq!(
+// editor.selections.ranges::<Point>(cx),
+// [
+// Point::new(0, 7)..Point::new(0, 7),
+// Point::new(1, 3)..Point::new(1, 3)
+// ]
+// );
+// editor
+// });
+// }
#[gpui::test]
async fn test_manipulate_lines_with_single_selection(cx: &mut TestAppContext) {
@@ -3055,99 +3060,100 @@ fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
});
}
-#[gpui::test]
-fn test_transpose(cx: &mut TestAppContext) {
- init_test(cx, |_| {});
+//todo!(test_transpose)
+// #[gpui::test]
+// fn test_transpose(cx: &mut TestAppContext) {
+// init_test(cx, |_| {});
- _ = cx.add_window(|cx| {
- let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
+// _ = cx.add_window(|cx| {
+// let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), cx);
- editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bac");
- assert_eq!(editor.selections.ranges(cx), [2..2]);
+// editor.change_selections(None, cx, |s| s.select_ranges([1..1]));
+// editor.transpose(&Default::default(), cx);
+// assert_eq!(editor.text(cx), "bac");
+// assert_eq!(editor.selections.ranges(cx), [2..2]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bca");
- assert_eq!(editor.selections.ranges(cx), [3..3]);
+// editor.transpose(&Default::default(), cx);
+// assert_eq!(editor.text(cx), "bca");
+// assert_eq!(editor.selections.ranges(cx), [3..3]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bac");
- assert_eq!(editor.selections.ranges(cx), [3..3]);
+// editor.transpose(&Default::default(), cx);
+// assert_eq!(editor.text(cx), "bac");
+// assert_eq!(editor.selections.ranges(cx), [3..3]);
- editor
- });
+// editor
+// });
- _ = cx.add_window(|cx| {
- let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
+// _ = cx.add_window(|cx| {
+// let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
- editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "acb\nde");
- assert_eq!(editor.selections.ranges(cx), [3..3]);
+// editor.change_selections(None, cx, |s| s.select_ranges([3..3]));
+// editor.transpose(&Default::default(), cx);
+// assert_eq!(editor.text(cx), "acb\nde");
+// assert_eq!(editor.selections.ranges(cx), [3..3]);
- editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "acbd\ne");
- assert_eq!(editor.selections.ranges(cx), [5..5]);
+// editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
+// editor.transpose(&Default::default(), cx);
+// assert_eq!(editor.text(cx), "acbd\ne");
+// assert_eq!(editor.selections.ranges(cx), [5..5]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "acbde\n");
- assert_eq!(editor.selections.ranges(cx), [6..6]);
+// editor.transpose(&Default::default(), cx);
+// assert_eq!(editor.text(cx), "acbde\n");
+// assert_eq!(editor.selections.ranges(cx), [6..6]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "acbd\ne");
- assert_eq!(editor.selections.ranges(cx), [6..6]);
+// editor.transpose(&Default::default(), cx);
+// assert_eq!(editor.text(cx), "acbd\ne");
+// assert_eq!(editor.selections.ranges(cx), [6..6]);
- editor
- });
+// editor
+// });
- _ = cx.add_window(|cx| {
- let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
+// _ = cx.add_window(|cx| {
+// let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), cx);
- editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bacd\ne");
- assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
+// editor.change_selections(None, cx, |s| s.select_ranges([1..1, 2..2, 4..4]));
+// editor.transpose(&Default::default(), cx);
+// assert_eq!(editor.text(cx), "bacd\ne");
+// assert_eq!(editor.selections.ranges(cx), [2..2, 3..3, 5..5]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bcade\n");
- assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
+// editor.transpose(&Default::default(), cx);
+// assert_eq!(editor.text(cx), "bcade\n");
+// assert_eq!(editor.selections.ranges(cx), [3..3, 4..4, 6..6]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bcda\ne");
- assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
+// editor.transpose(&Default::default(), cx);
+// assert_eq!(editor.text(cx), "bcda\ne");
+// assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bcade\n");
- assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
+// editor.transpose(&Default::default(), cx);
+// assert_eq!(editor.text(cx), "bcade\n");
+// assert_eq!(editor.selections.ranges(cx), [4..4, 6..6]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "bcaed\n");
- assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
+// editor.transpose(&Default::default(), cx);
+// assert_eq!(editor.text(cx), "bcaed\n");
+// assert_eq!(editor.selections.ranges(cx), [5..5, 6..6]);
- editor
- });
+// editor
+// });
- _ = cx.add_window(|cx| {
- let mut editor = build_editor(MultiBuffer::build_simple("ššā", cx), cx);
+// _ = cx.add_window(|cx| {
+// let mut editor = build_editor(MultiBuffer::build_simple("ššā", cx), cx);
- editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "ššā");
- assert_eq!(editor.selections.ranges(cx), [8..8]);
+// editor.change_selections(None, cx, |s| s.select_ranges([4..4]));
+// editor.transpose(&Default::default(), cx);
+// assert_eq!(editor.text(cx), "ššā");
+// assert_eq!(editor.selections.ranges(cx), [8..8]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "šāš");
- assert_eq!(editor.selections.ranges(cx), [11..11]);
+// editor.transpose(&Default::default(), cx);
+// assert_eq!(editor.text(cx), "šāš");
+// assert_eq!(editor.selections.ranges(cx), [11..11]);
- editor.transpose(&Default::default(), cx);
- assert_eq!(editor.text(cx), "ššā");
- assert_eq!(editor.selections.ranges(cx), [11..11]);
+// editor.transpose(&Default::default(), cx);
+// assert_eq!(editor.text(cx), "ššā");
+// assert_eq!(editor.selections.ranges(cx), [11..11]);
- editor
- });
-}
+// editor
+// });
+// }
//todo!(clipboard)
// #[gpui::test]