diff --git a/crates/collab2/src/tests/channel_buffer_tests.rs b/crates/collab2/src/tests/channel_buffer_tests.rs index 612832672d..63057cbd41 100644 --- a/crates/collab2/src/tests/channel_buffer_tests.rs +++ b/crates/collab2/src/tests/channel_buffer_tests.rs @@ -1,749 +1,379 @@ -use std::ops::Range; +//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}; +// 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") // #[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) // }); -// 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::() -// .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::() -// .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]); -// }); +// 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(); -// // 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); +// // 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()], +// ); // }); -// editor_b.update(cx_b, |editor, cx| { -// assert_remote_selections(editor, &[(Some(ParticipantIndex(0)), 0..1)], 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) // }); + +// // 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 // } -#[track_caller] -fn assert_remote_selections( - editor: &mut Editor, - expected_selections: &[(Option, Range)], - cx: &mut ViewContext, -) { - 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::>(); - assert_eq!( - remote_selections, expected_selections, - "incorrect remote selections" - ); -} +// // 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; -#[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 active_call_a = cx_a.read(ActiveCall::global); +// // let active_call_b = cx_b.read(ActiveCall::global); - let channel_id = server - .make_channel("the-channel", None, (&client_a, cx_a), &mut []) - .await; +// // cx_a.update(editor::init); +// // cx_b.update(editor::init); +// // cx_c.update(editor::init); - 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)); +// // let channel_id = server +// // .make_channel( +// // "the-channel", +// // None, +// // (&client_a, cx_a), +// // &mut [(&client_b, cx_b), (&client_c, cx_c)], +// // ) +// // .await; - // 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); +// // 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); - channel_buffer.update(cx_a, |buffer, cx| { - buffer.buffer().update(cx, |buffer, cx| { - buffer.edit([(0..0, "hello")], None, cx); - }) - }); - deterministic.run_until_parked(); +// // // 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(); - cx_a.update(|_| { - drop(channel_buffer); - drop(channel_buffer_2); - drop(channel_buffer_3); - }); - deterministic.run_until_parked(); +// // // 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]); +// // }); +// // }); +// // }); - // 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"); - }) - }); -} +// // // 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); +// // }); +// // }); -#[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; +// // // 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(); +// // } - let channel_id = server - .make_channel( - "the-channel", - None, - (&client_a, cx_a), - &mut [(&client_b, cx_b)], - ) - .await; +// // // 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, +// // ); +// // }); +// // }); - let channel_buffer_a = client_a - .channel_store() - .update(cx_a, |store, cx| store.open_channel_buffer(channel_id, cx)) - .await - .unwrap(); +// // // 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); - let channel_buffer_b = client_b - .channel_store() - .update(cx_b, |store, cx| store.open_channel_buffer(channel_id, cx)) - .await - .unwrap(); +// // // 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::() +// // .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::() +// // .unwrap(); - server.forbid_connections(); - server.disconnect_client(client_a.peer_id().unwrap()); - deterministic.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT); +// // 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(); - channel_buffer_a.update(cx_a, |buffer, cx| { - assert_eq!(buffer.channel(cx).unwrap().name, "the-channel"); - assert!(!buffer.is_connected()); - }); +// // // 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); +// // }); +// // } - 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( -// deterministic: BackgroundExecutor, -// mut cx_a: &mut TestAppContext, -// mut cx_b: &mut TestAppContext, -// mut cx_c: &mut TestAppContext, +// #[track_caller] +// fn assert_remote_selections( +// editor: &mut Editor, +// expected_selections: &[(Option, Range)], +// cx: &mut ViewContext, // ) { -// let mut server = TestServer::start(&deterministic).await; +// 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::>(); +// 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 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 -// .make_channel( -// "channel-2", -// None, -// (&client_a, cx_a), -// &mut [(&client_b, cx_b), (&client_c, cx_c)], -// ) +// let channel_id = server +// .make_channel("the-channel", None, (&client_a, cx_a), &mut []) // .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)) +// 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(); -// } -// deterministic.run_until_parked(); +// let channel_buffer_model_id = channel_buffer.entity_id(); +// assert_eq!(channel_buffer, channel_buffer_2); +// assert_eq!(channel_buffer, channel_buffer_3); -// // 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)) -// .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)) -// .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]); -// }); -// }); -// }); - -// // Client B follows client A. -// workspace_b -// .update(cx_b, |workspace, cx| { -// workspace.follow(client_a.peer_id().unwrap(), cx).unwrap() +// 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(); - -// // 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::() -// .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::(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)) -// .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::() -// .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"); +// 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"); +// }) // }); // } -//todo!(collab_ui) // #[gpui::test] -// async fn test_channel_buffer_changes( +// async fn test_channel_buffer_disconnect( // 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; @@ -762,7 +392,74 @@ async fn test_channel_buffers_and_server_restarts( // .await // .unwrap(); -// // Client A makes an edit, and client B should see that the note has changed. +// 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); @@ -770,105 +467,409 @@ async fn test_channel_buffers_and_server_restarts( // }); // deterministic.run_until_parked(); -// let has_buffer_changed = cx_b.update(|cx| { -// client_b -// .channel_store() -// .read(cx) -// .has_channel_buffer_changed(channel_id) -// .unwrap() -// }); -// assert!(has_buffer_changed); +// // Client A disconnects. +// server.forbid_connections(); +// server.disconnect_client(client_a.peer_id().unwrap()); -// // Opening the buffer should clear the changed flag. -// let project_b = client_b.build_empty_local_project(cx_b); -// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b); -// let channel_view_b = cx_b -// .update(|cx| ChannelView::open(channel_id, workspace_b.clone(), cx)) -// .await -// .unwrap(); -// deterministic.run_until_parked(); - -// let has_buffer_changed = cx_b.update(|cx| { -// client_b -// .channel_store() -// .read(cx) -// .has_channel_buffer_changed(channel_id) -// .unwrap() -// }); -// assert!(!has_buffer_changed); - -// // Editing the channel while the buffer is open should not show that the buffer has changed. +// // Both clients make an edit. // channel_buffer_a.update(cx_a, |buffer, cx| { // buffer.buffer().update(cx, |buffer, cx| { -// buffer.edit([(0..0, "2")], None, 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(); - -// let has_buffer_changed = cx_b.read(|cx| { -// client_b -// .channel_store() -// .read(cx) -// .has_channel_buffer_changed(channel_id) -// .unwrap() +// channel_buffer_a.read_with(cx_a, |buffer, cx| { +// assert_eq!(buffer.buffer().read(cx).text(), "12"); // }); -// assert!(!has_buffer_changed); - -// deterministic.advance_clock(ACKNOWLEDGE_DEBOUNCE_INTERVAL); - -// // Test that the server is tracking things correctly, and we retain our 'not changed' -// // state across a disconnect -// server.simulate_long_connection_interruption(client_b.peer_id().unwrap(), &deterministic); -// let has_buffer_changed = cx_b.read(|cx| { -// client_b -// .channel_store() -// .read(cx) -// .has_channel_buffer_changed(channel_id) -// .unwrap() +// channel_buffer_b.read_with(cx_b, |buffer, cx| { +// assert_eq!(buffer.buffer().read(cx).text(), "01"); // }); -// assert!(!has_buffer_changed); -// // Closing the buffer should re-enable change tracking -// cx_b.update(|cx| { -// workspace_b.update(cx, |workspace, cx| { -// workspace.close_all_items_and_panes(&Default::default(), cx) +// // 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()); // }); - -// drop(channel_view_b) // }); - -// deterministic.run_until_parked(); - -// channel_buffer_a.update(cx_a, |buffer, cx| { -// buffer.buffer().update(cx, |buffer, cx| { -// buffer.edit([(0..0, "3")], None, cx); -// }) -// }); -// deterministic.run_until_parked(); - -// let has_buffer_changed = cx_b.read(|cx| { -// client_b -// .channel_store() -// .read(cx) -// .has_channel_buffer_changed(channel_id) -// .unwrap() -// }); -// assert!(has_buffer_changed); // } -#[track_caller] -fn assert_collaborators(collaborators: &HashMap, ids: &[Option]) { - let mut user_ids = collaborators - .values() - .map(|collaborator| collaborator.user_id) - .collect::>(); - user_ids.sort(); - assert_eq!( - user_ids, - ids.into_iter().map(|id| id.unwrap()).collect::>() - ); -} +// #[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; -fn buffer_text(channel_buffer: &Model, cx: &mut TestAppContext) -> String { - channel_buffer.read_with(cx, |buffer, _| buffer.text()) -} +// 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( +// // deterministic: BackgroundExecutor, +// // mut cx_a: &mut TestAppContext, +// // mut cx_b: &mut TestAppContext, +// // mut cx_c: &mut TestAppContext, +// // ) { +// // let mut server = TestServer::start(&deterministic).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 +// // .make_channel( +// // "channel-2", +// // None, +// // (&client_a, cx_a), +// // &mut [(&client_b, cx_b), (&client_c, cx_c)], +// // ) +// // .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)) +// // .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)) +// // .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]); +// // }); +// // }); +// // }); + +// // // Client B follows client A. +// // workspace_b +// // .update(cx_b, |workspace, cx| { +// // workspace.follow(client_a.peer_id().unwrap(), cx).unwrap() +// // }) +// // .await +// // .unwrap(); + +// // // 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::() +// // .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::(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)) +// // .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::() +// // .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"); +// // }); +// // } + +// //todo!(collab_ui) +// // #[gpui::test] +// // async fn test_channel_buffer_changes( +// // deterministic: BackgroundExecutor, +// // cx_a: &mut TestAppContext, +// // cx_b: &mut TestAppContext, +// // ) { +// // let mut server = TestServer::start(&deterministic).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(); + +// // // Client A makes an edit, and client B should see that the note has changed. +// // 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(); + +// // let has_buffer_changed = cx_b.update(|cx| { +// // client_b +// // .channel_store() +// // .read(cx) +// // .has_channel_buffer_changed(channel_id) +// // .unwrap() +// // }); +// // assert!(has_buffer_changed); + +// // // Opening the buffer should clear the changed flag. +// // let project_b = client_b.build_empty_local_project(cx_b); +// // let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b); +// // let channel_view_b = cx_b +// // .update(|cx| ChannelView::open(channel_id, workspace_b.clone(), cx)) +// // .await +// // .unwrap(); +// // deterministic.run_until_parked(); + +// // let has_buffer_changed = cx_b.update(|cx| { +// // client_b +// // .channel_store() +// // .read(cx) +// // .has_channel_buffer_changed(channel_id) +// // .unwrap() +// // }); +// // assert!(!has_buffer_changed); + +// // // Editing the channel while the buffer is open should not show that the buffer has changed. +// // channel_buffer_a.update(cx_a, |buffer, cx| { +// // buffer.buffer().update(cx, |buffer, cx| { +// // buffer.edit([(0..0, "2")], None, cx); +// // }) +// // }); +// // deterministic.run_until_parked(); + +// // let has_buffer_changed = cx_b.read(|cx| { +// // client_b +// // .channel_store() +// // .read(cx) +// // .has_channel_buffer_changed(channel_id) +// // .unwrap() +// // }); +// // assert!(!has_buffer_changed); + +// // deterministic.advance_clock(ACKNOWLEDGE_DEBOUNCE_INTERVAL); + +// // // Test that the server is tracking things correctly, and we retain our 'not changed' +// // // state across a disconnect +// // server.simulate_long_connection_interruption(client_b.peer_id().unwrap(), &deterministic); +// // let has_buffer_changed = cx_b.read(|cx| { +// // client_b +// // .channel_store() +// // .read(cx) +// // .has_channel_buffer_changed(channel_id) +// // .unwrap() +// // }); +// // assert!(!has_buffer_changed); + +// // // Closing the buffer should re-enable change tracking +// // cx_b.update(|cx| { +// // workspace_b.update(cx, |workspace, cx| { +// // workspace.close_all_items_and_panes(&Default::default(), cx) +// // }); + +// // drop(channel_view_b) +// // }); + +// // deterministic.run_until_parked(); + +// // channel_buffer_a.update(cx_a, |buffer, cx| { +// // buffer.buffer().update(cx, |buffer, cx| { +// // buffer.edit([(0..0, "3")], None, cx); +// // }) +// // }); +// // deterministic.run_until_parked(); + +// // let has_buffer_changed = cx_b.read(|cx| { +// // client_b +// // .channel_store() +// // .read(cx) +// // .has_channel_buffer_changed(channel_id) +// // .unwrap() +// // }); +// // assert!(has_buffer_changed); +// // } + +// #[track_caller] +// fn assert_collaborators(collaborators: &HashMap, ids: &[Option]) { +// let mut user_ids = collaborators +// .values() +// .map(|collaborator| collaborator.user_id) +// .collect::>(); +// user_ids.sort(); +// assert_eq!( +// user_ids, +// ids.into_iter().map(|id| id.unwrap()).collect::>() +// ); +// } + +// fn buffer_text(channel_buffer: &Model, cx: &mut TestAppContext) -> String { +// channel_buffer.read_with(cx, |buffer, _| buffer.text()) +// } diff --git a/crates/collab2/src/tests/editor_tests.rs b/crates/collab2/src/tests/editor_tests.rs index 8fce187492..07a4269567 100644 --- a/crates/collab2/src/tests/editor_tests.rs +++ b/crates/collab2/src/tests/editor_tests.rs @@ -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::() - .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::(|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".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::( - |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::() - .unwrap(); - - let mut fake_language_server = fake_language_servers.next().await.unwrap(); - let mut requests = fake_language_server - .handle_request::(|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::(|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::(); - - // 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::( - |_, _| 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::() - .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::() - .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::(|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::(|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::() - .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::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::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::>(), - [ - 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::>(), - [ - 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::( - |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::(|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::(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::(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::() - .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::(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::() - .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::(()) - .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::(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::(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::() - .unwrap(); - - let editor_b = workspace_b - .update(cx_b, |workspace, cx| { - workspace.open_path((worktree_id, "main.rs"), None, true, cx) - }) - .await - .unwrap() - .downcast::() - .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::(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::(()) - .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 { - 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::() +// .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::(|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".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::( +// |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::() +// .unwrap(); + +// let mut fake_language_server = fake_language_servers.next().await.unwrap(); +// let mut requests = fake_language_server +// .handle_request::(|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::(|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::(); + +// // 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::( +// |_, _| 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::() +// .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::() +// .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::(|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::(|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::() +// .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::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::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::>(), +// [ +// 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::>(), +// [ +// 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::( +// |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::(|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::(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::(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::() +// .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::(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::() +// .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::(()) +// .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::(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::(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::() +// .unwrap(); + +// let editor_b = workspace_b +// .update(cx_b, |workspace, cx| { +// workspace.open_path((worktree_id, "main.rs"), None, true, cx) +// }) +// .await +// .unwrap() +// .downcast::() +// .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::(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::(()) +// .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 { +// 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 +// } diff --git a/crates/editor2/src/editor_tests.rs b/crates/editor2/src/editor_tests.rs index 0243d5a0b0..9798735bf6 100644 --- a/crates/editor2/src/editor_tests.rs +++ b/crates/editor2/src/editor_tests.rs @@ -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, |_| {}); +//todo!(editor navigate) +// #[gpui::test] +// async fn test_navigation_history(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); - use workspace::item::Item; +// use workspace::item::Item; - 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(); +// 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(); - 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))); +// 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 { - editor.nav_history.as_mut().unwrap().pop_backward(cx) - } +// fn pop_history(editor: &mut Editor, cx: &mut WindowContext) -> Option { +// 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()); +// // 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::::new(5.5, 5.5), cx); - let original_scroll_position = editor.scroll_manager.anchor(); +// // Set scroll position to check later +// editor.set_scroll_position(gpui::Point::::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::::new(-2.5, -0.5), cx); - assert_ne!(editor.scroll_manager.anchor(), original_scroll_position); +// // Jump to the end of the document and adjust scroll +// editor.move_to_end(&MoveToEnd, cx); +// editor.set_scroll_position(gpui::Point::::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); +// 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) - ); +// // 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, |_| {}); +//todo!(select_anchor_ranges) +// #[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(); +// 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::(cx), - &[Point::new(0, 0)..Point::new(0, 0)] - ); +// assert_eq!( +// editor.selections.ranges::(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::(cx), - &[Point::new(0, 3)..Point::new(0, 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::(cx), +// &[Point::new(0, 3)..Point::new(0, 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::(cx), - &[Point::new(0, 11)..Point::new(0, 11)] - ); +// // 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::(cx), +// &[Point::new(0, 11)..Point::new(0, 11)] +// ); - // 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::(cx), - &[Point::new(0, 5)..Point::new(2, 2)] - ); +// // 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::(cx), +// &[Point::new(0, 5)..Point::new(2, 2)] +// ); - // 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::(cx), - [Point::new(2, 3)..Point::new(2, 3)] - ); +// // 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::(cx), +// [Point::new(2, 3)..Point::new(2, 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::(cx), - [Point::new(2, 3)..Point::new(2, 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::(cx), +// [Point::new(2, 3)..Point::new(2, 3)] +// ); - // 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::(cx), - [Point::new(2, 3)..Point::new(2, 3)] - ); +// // 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::(cx), +// [Point::new(2, 3)..Point::new(2, 3)] +// ); - // 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, - ) - }); +// // 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, +// ) +// }); - // 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 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 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 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 ignore any leading tabs - editor.join_lines(&JoinLines, cx); - assert_eq!(buffer.read(cx).text(), "aaa bbb c d"); +// // We ignore any leading tabs +// editor.join_lines(&JoinLines, cx); +// assert_eq!(buffer.read(cx).text(), "aaa bbb c d"); - editor - }); -} +// editor +// }); +// } -#[gpui::test] -fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) { - init_test(cx, |_| {}); +// #[gpui::test] +// fn test_join_lines_with_multi_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(); +// 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.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"); +// editor.join_lines(&JoinLines, cx); +// assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n"); - assert_eq!( - editor.selections.ranges::(cx), - [ - Point::new(0, 7)..Point::new(0, 7), - Point::new(1, 3)..Point::new(1, 3) - ] - ); - editor - }); -} +// assert_eq!( +// editor.selections.ranges::(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] @@ -4805,114 +4811,115 @@ async fn test_delete_autoclose_pair(cx: &mut gpui::TestAppContext) { }); } -#[gpui::test] -async fn test_snippets(cx: &mut gpui::TestAppContext) { - init_test(cx, |_| {}); +// todo!(select_anchor_ranges) +// #[gpui::test] +// async fn test_snippets(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); - let (text, insertion_ranges) = marked_text_ranges( - indoc! {" - a.ˇ b - a.ˇ b - a.ˇ b - "}, - false, - ); +// let (text, insertion_ranges) = marked_text_ranges( +// indoc! {" +// a.ˇ b +// a.ˇ b +// a.ˇ b +// "}, +// false, +// ); - let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx)); - let (editor, mut cx) = cx.add_window_view(|cx| build_editor(buffer, cx)); - let cx = &mut cx; +// let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx)); +// let (editor, mut cx) = cx.add_window_view(|cx| build_editor(buffer, cx)); +// let cx = &mut cx; - editor.update(cx, |editor, cx| { - let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap(); +// editor.update(cx, |editor, cx| { +// let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap(); - editor - .insert_snippet(&insertion_ranges, snippet, cx) - .unwrap(); +// editor +// .insert_snippet(&insertion_ranges, snippet, cx) +// .unwrap(); - fn assert(editor: &mut Editor, cx: &mut ViewContext, marked_text: &str) { - let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false); - assert_eq!(editor.text(cx), expected_text); - assert_eq!(editor.selections.ranges::(cx), selection_ranges); - } +// fn assert(editor: &mut Editor, cx: &mut ViewContext, marked_text: &str) { +// let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false); +// assert_eq!(editor.text(cx), expected_text); +// assert_eq!(editor.selections.ranges::(cx), selection_ranges); +// } - assert( - editor, - cx, - indoc! {" - a.f(«one», two, «three») b - a.f(«one», two, «three») b - a.f(«one», two, «three») b - "}, - ); +// assert( +// editor, +// cx, +// indoc! {" +// a.f(«one», two, «three») b +// a.f(«one», two, «three») b +// a.f(«one», two, «three») b +// "}, +// ); - // Can't move earlier than the first tab stop - assert!(!editor.move_to_prev_snippet_tabstop(cx)); - assert( - editor, - cx, - indoc! {" - a.f(«one», two, «three») b - a.f(«one», two, «three») b - a.f(«one», two, «three») b - "}, - ); +// // Can't move earlier than the first tab stop +// assert!(!editor.move_to_prev_snippet_tabstop(cx)); +// assert( +// editor, +// cx, +// indoc! {" +// a.f(«one», two, «three») b +// a.f(«one», two, «three») b +// a.f(«one», two, «three») b +// "}, +// ); - assert!(editor.move_to_next_snippet_tabstop(cx)); - assert( - editor, - cx, - indoc! {" - a.f(one, «two», three) b - a.f(one, «two», three) b - a.f(one, «two», three) b - "}, - ); +// assert!(editor.move_to_next_snippet_tabstop(cx)); +// assert( +// editor, +// cx, +// indoc! {" +// a.f(one, «two», three) b +// a.f(one, «two», three) b +// a.f(one, «two», three) b +// "}, +// ); - editor.move_to_prev_snippet_tabstop(cx); - assert( - editor, - cx, - indoc! {" - a.f(«one», two, «three») b - a.f(«one», two, «three») b - a.f(«one», two, «three») b - "}, - ); +// editor.move_to_prev_snippet_tabstop(cx); +// assert( +// editor, +// cx, +// indoc! {" +// a.f(«one», two, «three») b +// a.f(«one», two, «three») b +// a.f(«one», two, «three») b +// "}, +// ); - assert!(editor.move_to_next_snippet_tabstop(cx)); - assert( - editor, - cx, - indoc! {" - a.f(one, «two», three) b - a.f(one, «two», three) b - a.f(one, «two», three) b - "}, - ); - assert!(editor.move_to_next_snippet_tabstop(cx)); - assert( - editor, - cx, - indoc! {" - a.f(one, two, three)ˇ b - a.f(one, two, three)ˇ b - a.f(one, two, three)ˇ b - "}, - ); +// assert!(editor.move_to_next_snippet_tabstop(cx)); +// assert( +// editor, +// cx, +// indoc! {" +// a.f(one, «two», three) b +// a.f(one, «two», three) b +// a.f(one, «two», three) b +// "}, +// ); +// assert!(editor.move_to_next_snippet_tabstop(cx)); +// assert( +// editor, +// cx, +// indoc! {" +// a.f(one, two, three)ˇ b +// a.f(one, two, three)ˇ b +// a.f(one, two, three)ˇ b +// "}, +// ); - // As soon as the last tab stop is reached, snippet state is gone - editor.move_to_prev_snippet_tabstop(cx); - assert( - editor, - cx, - indoc! {" - a.f(one, two, three)ˇ b - a.f(one, two, three)ˇ b - a.f(one, two, three)ˇ b - "}, - ); - }); -} +// // As soon as the last tab stop is reached, snippet state is gone +// editor.move_to_prev_snippet_tabstop(cx); +// assert( +// editor, +// cx, +// indoc! {" +// a.f(one, two, three)ˇ b +// a.f(one, two, three)ˇ b +// a.f(one, two, three)ˇ b +// "}, +// ); +// }); +// } #[gpui::test] async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) { @@ -6328,254 +6335,255 @@ async fn test_extra_newline_insertion(cx: &mut gpui::TestAppContext) { }); } -#[gpui::test] -fn test_highlighted_ranges(cx: &mut TestAppContext) { - init_test(cx, |_| {}); +//todo!(finish editor tests) +// #[gpui::test] +// fn test_highlighted_ranges(cx: &mut TestAppContext) { +// init_test(cx, |_| {}); - let editor = cx.add_window(|cx| { - let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx); - build_editor(buffer.clone(), cx) - }); +// let editor = cx.add_window(|cx| { +// let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx); +// build_editor(buffer.clone(), cx) +// }); - editor.update(cx, |editor, cx| { - struct Type1; - struct Type2; +// editor.update(cx, |editor, cx| { +// struct Type1; +// struct Type2; - let buffer = editor.buffer.read(cx).snapshot(cx); +// let buffer = editor.buffer.read(cx).snapshot(cx); - let anchor_range = - |range: Range| buffer.anchor_after(range.start)..buffer.anchor_after(range.end); +// let anchor_range = +// |range: Range| buffer.anchor_after(range.start)..buffer.anchor_after(range.end); - editor.highlight_background::( - vec![ - anchor_range(Point::new(2, 1)..Point::new(2, 3)), - anchor_range(Point::new(4, 2)..Point::new(4, 4)), - anchor_range(Point::new(6, 3)..Point::new(6, 5)), - anchor_range(Point::new(8, 4)..Point::new(8, 6)), - ], - |_| Hsla::red(), - cx, - ); - editor.highlight_background::( - vec![ - anchor_range(Point::new(3, 2)..Point::new(3, 5)), - anchor_range(Point::new(5, 3)..Point::new(5, 6)), - anchor_range(Point::new(7, 4)..Point::new(7, 7)), - anchor_range(Point::new(9, 5)..Point::new(9, 8)), - ], - |_| Hsla::green(), - cx, - ); +// editor.highlight_background::( +// vec![ +// anchor_range(Point::new(2, 1)..Point::new(2, 3)), +// anchor_range(Point::new(4, 2)..Point::new(4, 4)), +// anchor_range(Point::new(6, 3)..Point::new(6, 5)), +// anchor_range(Point::new(8, 4)..Point::new(8, 6)), +// ], +// |_| Hsla::red(), +// cx, +// ); +// editor.highlight_background::( +// vec![ +// anchor_range(Point::new(3, 2)..Point::new(3, 5)), +// anchor_range(Point::new(5, 3)..Point::new(5, 6)), +// anchor_range(Point::new(7, 4)..Point::new(7, 7)), +// anchor_range(Point::new(9, 5)..Point::new(9, 8)), +// ], +// |_| Hsla::green(), +// cx, +// ); - let snapshot = editor.snapshot(cx); - let mut highlighted_ranges = editor.background_highlights_in_range( - anchor_range(Point::new(3, 4)..Point::new(7, 4)), - &snapshot, - cx.theme().colors(), - ); - // Enforce a consistent ordering based on color without relying on the ordering of the - // highlight's `TypeId` which is non-executor. - highlighted_ranges.sort_unstable_by_key(|(_, color)| *color); - assert_eq!( - highlighted_ranges, - &[ - ( - DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5), - Hsla::green(), - ), - ( - DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6), - Hsla::green(), - ), - ( - DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4), - Hsla::red(), - ), - ( - DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5), - Hsla::red(), - ), - ] - ); - assert_eq!( - editor.background_highlights_in_range( - anchor_range(Point::new(5, 6)..Point::new(6, 4)), - &snapshot, - cx.theme().colors(), - ), - &[( - DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5), - Hsla::red(), - )] - ); - }); -} +// let snapshot = editor.snapshot(cx); +// let mut highlighted_ranges = editor.background_highlights_in_range( +// anchor_range(Point::new(3, 4)..Point::new(7, 4)), +// &snapshot, +// cx.theme().colors(), +// ); +// // Enforce a consistent ordering based on color without relying on the ordering of the +// // highlight's `TypeId` which is non-executor. +// highlighted_ranges.sort_unstable_by_key(|(_, color)| *color); +// assert_eq!( +// highlighted_ranges, +// &[ +// ( +// DisplayPoint::new(3, 2)..DisplayPoint::new(3, 5), +// Hsla::green(), +// ), +// ( +// DisplayPoint::new(5, 3)..DisplayPoint::new(5, 6), +// Hsla::green(), +// ), +// ( +// DisplayPoint::new(4, 2)..DisplayPoint::new(4, 4), +// Hsla::red(), +// ), +// ( +// DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5), +// Hsla::red(), +// ), +// ] +// ); +// assert_eq!( +// editor.background_highlights_in_range( +// anchor_range(Point::new(5, 6)..Point::new(6, 4)), +// &snapshot, +// cx.theme().colors(), +// ), +// &[( +// DisplayPoint::new(6, 3)..DisplayPoint::new(6, 5), +// Hsla::red(), +// )] +// ); +// }); +// } -#[gpui::test] -async fn test_following(cx: &mut gpui::TestAppContext) { - init_test(cx, |_| {}); +// todo!(following) +// #[gpui::test] +// async fn test_following(cx: &mut gpui::TestAppContext) { +// init_test(cx, |_| {}); - let fs = FakeFs::new(cx.executor()); - let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; +// let fs = FakeFs::new(cx.executor()); +// let project = Project::test(fs, ["/file.rs".as_ref()], cx).await; - let buffer = project.update(cx, |project, cx| { - let buffer = project - .create_buffer(&sample_text(16, 8, 'a'), None, cx) - .unwrap(); - cx.build_model(|cx| MultiBuffer::singleton(buffer, cx)) - }); - let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx)); - let follower = cx.update(|cx| { - cx.open_window( - WindowOptions { - bounds: WindowBounds::Fixed(Bounds::from_corners( - gpui::Point::new((0. as f64).into(), (0. as f64).into()), - gpui::Point::new((10. as f64).into(), (80. as f64).into()), - )), - ..Default::default() - }, - |cx| cx.build_view(|cx| build_editor(buffer.clone(), cx)), - ) - }); +// let buffer = project.update(cx, |project, cx| { +// let buffer = project +// .create_buffer(&sample_text(16, 8, 'a'), None, cx) +// .unwrap(); +// cx.build_model(|cx| MultiBuffer::singleton(buffer, cx)) +// }); +// let leader = cx.add_window(|cx| build_editor(buffer.clone(), cx)); +// let follower = cx.update(|cx| { +// cx.open_window( +// WindowOptions { +// bounds: WindowBounds::Fixed(Bounds::from_corners( +// gpui::Point::new((0. as f64).into(), (0. as f64).into()), +// gpui::Point::new((10. as f64).into(), (80. as f64).into()), +// )), +// ..Default::default() +// }, +// |cx| cx.build_view(|cx| build_editor(buffer.clone(), cx)), +// ) +// }); - let is_still_following = Rc::new(RefCell::new(true)); - let follower_edit_event_count = Rc::new(RefCell::new(0)); - let pending_update = Rc::new(RefCell::new(None)); - follower.update(cx, { - let update = pending_update.clone(); - let is_still_following = is_still_following.clone(); - let follower_edit_event_count = follower_edit_event_count.clone(); - |_, cx| { - cx.subscribe( - &leader.root_view(cx).unwrap(), - move |_, leader, event, cx| { - leader - .read(cx) - .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx); - }, - ) - .detach(); +// let is_still_following = Rc::new(RefCell::new(true)); +// let follower_edit_event_count = Rc::new(RefCell::new(0)); +// let pending_update = Rc::new(RefCell::new(None)); +// follower.update(cx, { +// let update = pending_update.clone(); +// let is_still_following = is_still_following.clone(); +// let follower_edit_event_count = follower_edit_event_count.clone(); +// |_, cx| { +// cx.subscribe( +// &leader.root_view(cx).unwrap(), +// move |_, leader, event, cx| { +// leader +// .read(cx) +// .add_event_to_update_proto(event, &mut *update.borrow_mut(), cx); +// }, +// ) +// .detach(); - cx.subscribe( - &follower.root_view(cx).unwrap(), - move |_, _, event: &Event, cx| { - if matches!(event.to_follow_event(), Some(FollowEvent::Unfollow)) { - *is_still_following.borrow_mut() = false; - } +// cx.subscribe( +// &follower.root_view(cx).unwrap(), +// move |_, _, event: &Event, cx| { +// if matches!(event.to_follow_event(), Some(FollowEvent::Unfollow)) { +// *is_still_following.borrow_mut() = false; +// } - if let Event::BufferEdited = event { - *follower_edit_event_count.borrow_mut() += 1; - } - }, - ) - .detach(); - } - }); +// if let Event::BufferEdited = event { +// *follower_edit_event_count.borrow_mut() += 1; +// } +// }, +// ) +// .detach(); +// } +// }); - // Update the selections only - leader.update(cx, |leader, cx| { - leader.change_selections(None, cx, |s| s.select_ranges([1..1])); - }); - follower - .update(cx, |follower, cx| { - follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx) - }) - .unwrap() - .await - .unwrap(); - follower.update(cx, |follower, cx| { - assert_eq!(follower.selections.ranges(cx), vec![1..1]); - }); - assert_eq!(*is_still_following.borrow(), true); - assert_eq!(*follower_edit_event_count.borrow(), 0); +// // Update the selections only +// leader.update(cx, |leader, cx| { +// leader.change_selections(None, cx, |s| s.select_ranges([1..1])); +// }); +// follower +// .update(cx, |follower, cx| { +// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// follower.update(cx, |follower, cx| { +// assert_eq!(follower.selections.ranges(cx), vec![1..1]); +// }); +// assert_eq!(*is_still_following.borrow(), true); +// assert_eq!(*follower_edit_event_count.borrow(), 0); - // Update the scroll position only - leader.update(cx, |leader, cx| { - leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx); - }); - follower - .update(cx, |follower, cx| { - follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx) - }) - .unwrap() - .await - .unwrap(); - assert_eq!( - follower - .update(cx, |follower, cx| follower.scroll_position(cx)) - .unwrap(), - gpui::Point::new(1.5, 3.5) - ); - assert_eq!(*is_still_following.borrow(), true); - assert_eq!(*follower_edit_event_count.borrow(), 0); +// // Update the scroll position only +// leader.update(cx, |leader, cx| { +// leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx); +// }); +// follower +// .update(cx, |follower, cx| { +// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// assert_eq!( +// follower +// .update(cx, |follower, cx| follower.scroll_position(cx)) +// .unwrap(), +// gpui::Point::new(1.5, 3.5) +// ); +// assert_eq!(*is_still_following.borrow(), true); +// assert_eq!(*follower_edit_event_count.borrow(), 0); - // Update the selections and scroll position. The follower's scroll position is updated - // via autoscroll, not via the leader's exact scroll position. - leader.update(cx, |leader, cx| { - leader.change_selections(None, cx, |s| s.select_ranges([0..0])); - leader.request_autoscroll(Autoscroll::newest(), cx); - leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx); - }); - follower - .update(cx, |follower, cx| { - follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx) - }) - .unwrap() - .await - .unwrap(); - follower.update(cx, |follower, cx| { - assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0)); - assert_eq!(follower.selections.ranges(cx), vec![0..0]); - }); - assert_eq!(*is_still_following.borrow(), true); +// // Update the selections and scroll position. The follower's scroll position is updated +// // via autoscroll, not via the leader's exact scroll position. +// leader.update(cx, |leader, cx| { +// leader.change_selections(None, cx, |s| s.select_ranges([0..0])); +// leader.request_autoscroll(Autoscroll::newest(), cx); +// leader.set_scroll_position(gpui::Point::new(1.5, 3.5), cx); +// }); +// follower +// .update(cx, |follower, cx| { +// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// follower.update(cx, |follower, cx| { +// assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0)); +// assert_eq!(follower.selections.ranges(cx), vec![0..0]); +// }); +// assert_eq!(*is_still_following.borrow(), true); - // Creating a pending selection that precedes another selection - leader.update(cx, |leader, cx| { - leader.change_selections(None, cx, |s| s.select_ranges([1..1])); - leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx); - }); - follower - .update(cx, |follower, cx| { - follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx) - }) - .unwrap() - .await - .unwrap(); - follower.update(cx, |follower, cx| { - assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]); - }); - assert_eq!(*is_still_following.borrow(), true); +// // Creating a pending selection that precedes another selection +// leader.update(cx, |leader, cx| { +// leader.change_selections(None, cx, |s| s.select_ranges([1..1])); +// leader.begin_selection(DisplayPoint::new(0, 0), true, 1, cx); +// }); +// follower +// .update(cx, |follower, cx| { +// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// follower.update(cx, |follower, cx| { +// assert_eq!(follower.selections.ranges(cx), vec![0..0, 1..1]); +// }); +// assert_eq!(*is_still_following.borrow(), true); - // Extend the pending selection so that it surrounds another selection - leader.update(cx, |leader, cx| { - leader.extend_selection(DisplayPoint::new(0, 2), 1, cx); - }); - follower - .update(cx, |follower, cx| { - follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx) - }) - .unwrap() - .await - .unwrap(); - follower.update(cx, |follower, cx| { - assert_eq!(follower.selections.ranges(cx), vec![0..2]); - }); +// // Extend the pending selection so that it surrounds another selection +// leader.update(cx, |leader, cx| { +// leader.extend_selection(DisplayPoint::new(0, 2), 1, cx); +// }); +// follower +// .update(cx, |follower, cx| { +// follower.apply_update_proto(&project, pending_update.borrow_mut().take().unwrap(), cx) +// }) +// .unwrap() +// .await +// .unwrap(); +// follower.update(cx, |follower, cx| { +// assert_eq!(follower.selections.ranges(cx), vec![0..2]); +// }); - // Scrolling locally breaks the follow - follower.update(cx, |follower, cx| { - let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0); - follower.set_scroll_anchor( - ScrollAnchor { - anchor: top_anchor, - offset: gpui::Point::new(0.0, 0.5), - }, - cx, - ); - }); - assert_eq!(*is_still_following.borrow(), false); -} +// // Scrolling locally breaks the follow +// follower.update(cx, |follower, cx| { +// let top_anchor = follower.buffer().read(cx).read(cx).anchor_after(0); +// follower.set_scroll_anchor( +// ScrollAnchor { +// anchor: top_anchor, +// offset: gpui::Point::new(0.0, 0.5), +// }, +// cx, +// ); +// }); +// assert_eq!(*is_still_following.borrow(), false); +// } -//todo!(following) // #[gpui::test] // async fn test_following_with_multiple_excerpts(cx: &mut gpui::TestAppContext) { // init_test(cx, |_| {}); diff --git a/crates/editor2/src/test/editor_lsp_test_context.rs b/crates/editor2/src/test/editor_lsp_test_context.rs index afcefad6b2..7ee55cddba 100644 --- a/crates/editor2/src/test/editor_lsp_test_context.rs +++ b/crates/editor2/src/test/editor_lsp_test_context.rs @@ -59,6 +59,7 @@ impl<'a> EditorLspTestContext<'a> { .await; let project = Project::test(app_state.fs.clone(), [], cx).await; + project.update(cx, |project, _| project.languages().add(Arc::new(language))); app_state @@ -68,7 +69,9 @@ impl<'a> EditorLspTestContext<'a> { .await; let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx)); + let workspace = window.root_view(cx).unwrap(); + let mut cx = VisualTestContext::from_window(*window.deref(), cx); project .update(&mut cx, |project, cx| { @@ -78,7 +81,6 @@ impl<'a> EditorLspTestContext<'a> { .unwrap(); cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx)) .await; - let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone()); let item = workspace .update(&mut cx, |workspace, cx| { @@ -86,7 +88,6 @@ impl<'a> EditorLspTestContext<'a> { }) .await .expect("Could not open test file"); - let editor = cx.update(|cx| { item.act_as::(cx) .expect("Opened test file wasn't an editor") @@ -94,7 +95,6 @@ impl<'a> EditorLspTestContext<'a> { editor.update(&mut cx, |editor, cx| editor.focus(cx)); let lsp = fake_servers.next().await.unwrap(); - Self { cx: EditorTestContext { cx, diff --git a/crates/gpui2/src/elements/text.rs b/crates/gpui2/src/elements/text.rs index 5c5709d32e..eee0584460 100644 --- a/crates/gpui2/src/elements/text.rs +++ b/crates/gpui2/src/elements/text.rs @@ -81,6 +81,7 @@ impl Element for Text { let text = self.text.clone(); let rem_size = cx.rem_size(); + let layout_id = cx.request_measured_layout(Default::default(), rem_size, { let element_state = element_state.clone(); move |known_dimensions, _| { @@ -93,6 +94,10 @@ impl Element for Text { ) .log_err() else { + element_state.lock().replace(TextElementState { + lines: Default::default(), + line_height, + }); return Size::default(); }; @@ -131,7 +136,8 @@ impl Element for Text { let element_state = element_state.lock(); let element_state = element_state .as_ref() - .expect("measurement has not been performed"); + .ok_or_else(|| anyhow::anyhow!("measurement has not been performed on {}", &self.text)) + .unwrap(); let line_height = element_state.line_height; let mut line_origin = bounds.origin; diff --git a/crates/gpui2/src/window.rs b/crates/gpui2/src/window.rs index fbc9cff1b5..425fb9510f 100644 --- a/crates/gpui2/src/window.rs +++ b/crates/gpui2/src/window.rs @@ -2138,7 +2138,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { &mut self, handler: impl Fn(&mut V, &Event, DispatchPhase, &mut ViewContext) + 'static, ) { - let handle = self.view(); + let handle = self.view().clone(); self.window_cx.on_key_event(move |event, phase, cx| { handle.update(cx, |view, cx| { handler(view, event, phase, cx); @@ -2151,7 +2151,7 @@ impl<'a, V: 'static> ViewContext<'a, V> { action_type: TypeId, handler: impl Fn(&mut V, &dyn Any, DispatchPhase, &mut ViewContext) + 'static, ) { - let handle = self.view(); + let handle = self.view().clone(); self.window_cx .on_action(action_type, move |action, phase, cx| { handle.update(cx, |view, cx| {