editor_tests.rs

   1// use editor::{
   2//     test::editor_test_context::EditorTestContext, ConfirmCodeAction, ConfirmCompletion,
   3//     ConfirmRename, Editor, Redo, Rename, ToggleCodeActions, Undo,
   4// };
   5
   6//todo!(editor)
   7// #[gpui::test(iterations = 10)]
   8// async fn test_host_disconnect(
   9//     executor: BackgroundExecutor,
  10//     cx_a: &mut TestAppContext,
  11//     cx_b: &mut TestAppContext,
  12//     cx_c: &mut TestAppContext,
  13// ) {
  14//     let mut server = TestServer::start(&executor).await;
  15//     let client_a = server.create_client(cx_a, "user_a").await;
  16//     let client_b = server.create_client(cx_b, "user_b").await;
  17//     let client_c = server.create_client(cx_c, "user_c").await;
  18//     server
  19//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
  20//         .await;
  21
  22//     cx_b.update(editor::init);
  23
  24//     client_a
  25//         .fs()
  26//         .insert_tree(
  27//             "/a",
  28//             json!({
  29//                 "a.txt": "a-contents",
  30//                 "b.txt": "b-contents",
  31//             }),
  32//         )
  33//         .await;
  34
  35//     let active_call_a = cx_a.read(ActiveCall::global);
  36//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
  37
  38//     let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees(cx).next().unwrap());
  39//     let project_id = active_call_a
  40//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
  41//         .await
  42//         .unwrap();
  43
  44//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
  45//     executor.run_until_parked();
  46
  47//     assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
  48
  49//     let window_b =
  50//         cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
  51//     let workspace_b = window_b.root(cx_b);
  52//     let editor_b = workspace_b
  53//         .update(cx_b, |workspace, cx| {
  54//             workspace.open_path((worktree_id, "b.txt"), None, true, cx)
  55//         })
  56//         .await
  57//         .unwrap()
  58//         .downcast::<Editor>()
  59//         .unwrap();
  60
  61//     assert!(window_b.read_with(cx_b, |cx| editor_b.is_focused(cx)));
  62//     editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
  63//     assert!(window_b.is_edited(cx_b));
  64
  65//     // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
  66//     server.forbid_connections();
  67//     server.disconnect_client(client_a.peer_id().unwrap());
  68//     executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
  69
  70//     project_a.read_with(cx_a, |project, _| project.collaborators().is_empty());
  71
  72//     project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
  73
  74//     project_b.read_with(cx_b, |project, _| project.is_read_only());
  75
  76//     assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
  77
  78//     // Ensure client B's edited state is reset and that the whole window is blurred.
  79
  80//     window_b.read_with(cx_b, |cx| {
  81//         assert_eq!(cx.focused_view_id(), None);
  82//     });
  83//     assert!(!window_b.is_edited(cx_b));
  84
  85//     // Ensure client B is not prompted to save edits when closing window after disconnecting.
  86//     let can_close = workspace_b
  87//         .update(cx_b, |workspace, cx| workspace.prepare_to_close(true, cx))
  88//         .await
  89//         .unwrap();
  90//     assert!(can_close);
  91
  92//     // Allow client A to reconnect to the server.
  93//     server.allow_connections();
  94//     executor.advance_clock(RECEIVE_TIMEOUT);
  95
  96//     // Client B calls client A again after they reconnected.
  97//     let active_call_b = cx_b.read(ActiveCall::global);
  98//     active_call_b
  99//         .update(cx_b, |call, cx| {
 100//             call.invite(client_a.user_id().unwrap(), None, cx)
 101//         })
 102//         .await
 103//         .unwrap();
 104//     executor.run_until_parked();
 105//     active_call_a
 106//         .update(cx_a, |call, cx| call.accept_incoming(cx))
 107//         .await
 108//         .unwrap();
 109
 110//     active_call_a
 111//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 112//         .await
 113//         .unwrap();
 114
 115//     // Drop client A's connection again. We should still unshare it successfully.
 116//     server.forbid_connections();
 117//     server.disconnect_client(client_a.peer_id().unwrap());
 118//     executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
 119
 120//     project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
 121// }
 122
 123//todo!(editor)
 124// #[gpui::test]
 125// async fn test_newline_above_or_below_does_not_move_guest_cursor(
 126//     executor: BackgroundExecutor,
 127//     cx_a: &mut TestAppContext,
 128//     cx_b: &mut TestAppContext,
 129// ) {
 130//     let mut server = TestServer::start(&executor).await;
 131//     let client_a = server.create_client(cx_a, "user_a").await;
 132//     let client_b = server.create_client(cx_b, "user_b").await;
 133//     server
 134//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 135//         .await;
 136//     let active_call_a = cx_a.read(ActiveCall::global);
 137
 138//     client_a
 139//         .fs()
 140//         .insert_tree("/dir", json!({ "a.txt": "Some text\n" }))
 141//         .await;
 142//     let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
 143//     let project_id = active_call_a
 144//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 145//         .await
 146//         .unwrap();
 147
 148//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
 149
 150//     // Open a buffer as client A
 151//     let buffer_a = project_a
 152//         .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 153//         .await
 154//         .unwrap();
 155//     let window_a = cx_a.add_window(|_| EmptyView);
 156//     let editor_a = window_a.add_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
 157//     let mut editor_cx_a = EditorTestContext {
 158//         cx: cx_a,
 159//         window: window_a.into(),
 160//         editor: editor_a,
 161//     };
 162
 163//     // Open a buffer as client B
 164//     let buffer_b = project_b
 165//         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
 166//         .await
 167//         .unwrap();
 168//     let window_b = cx_b.add_window(|_| EmptyView);
 169//     let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
 170//     let mut editor_cx_b = EditorTestContext {
 171//         cx: cx_b,
 172//         window: window_b.into(),
 173//         editor: editor_b,
 174//     };
 175
 176//     // Test newline above
 177//     editor_cx_a.set_selections_state(indoc! {"
 178//         Some textˇ
 179//     "});
 180//     editor_cx_b.set_selections_state(indoc! {"
 181//         Some textˇ
 182//     "});
 183//     editor_cx_a.update_editor(|editor, cx| editor.newline_above(&editor::NewlineAbove, cx));
 184//     executor.run_until_parked();
 185//     editor_cx_a.assert_editor_state(indoc! {"
 186//         ˇ
 187//         Some text
 188//     "});
 189//     editor_cx_b.assert_editor_state(indoc! {"
 190
 191//         Some textˇ
 192//     "});
 193
 194//     // Test newline below
 195//     editor_cx_a.set_selections_state(indoc! {"
 196
 197//         Some textˇ
 198//     "});
 199//     editor_cx_b.set_selections_state(indoc! {"
 200
 201//         Some textˇ
 202//     "});
 203//     editor_cx_a.update_editor(|editor, cx| editor.newline_below(&editor::NewlineBelow, cx));
 204//     executor.run_until_parked();
 205//     editor_cx_a.assert_editor_state(indoc! {"
 206
 207//         Some text
 208//         ˇ
 209//     "});
 210//     editor_cx_b.assert_editor_state(indoc! {"
 211
 212//         Some textˇ
 213
 214//     "});
 215// }
 216
 217//todo!(editor)
 218// #[gpui::test(iterations = 10)]
 219// async fn test_collaborating_with_completion(
 220//     executor: BackgroundExecutor,
 221//     cx_a: &mut TestAppContext,
 222//     cx_b: &mut TestAppContext,
 223// ) {
 224//     let mut server = TestServer::start(&executor).await;
 225//     let client_a = server.create_client(cx_a, "user_a").await;
 226//     let client_b = server.create_client(cx_b, "user_b").await;
 227//     server
 228//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 229//         .await;
 230//     let active_call_a = cx_a.read(ActiveCall::global);
 231
 232//     // Set up a fake language server.
 233//     let mut language = Language::new(
 234//         LanguageConfig {
 235//             name: "Rust".into(),
 236//             path_suffixes: vec!["rs".to_string()],
 237//             ..Default::default()
 238//         },
 239//         Some(tree_sitter_rust::language()),
 240//     );
 241//     let mut fake_language_servers = language
 242//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
 243//             capabilities: lsp::ServerCapabilities {
 244//                 completion_provider: Some(lsp::CompletionOptions {
 245//                     trigger_characters: Some(vec![".".to_string()]),
 246//                     resolve_provider: Some(true),
 247//                     ..Default::default()
 248//                 }),
 249//                 ..Default::default()
 250//             },
 251//             ..Default::default()
 252//         }))
 253//         .await;
 254//     client_a.language_registry().add(Arc::new(language));
 255
 256//     client_a
 257//         .fs()
 258//         .insert_tree(
 259//             "/a",
 260//             json!({
 261//                 "main.rs": "fn main() { a }",
 262//                 "other.rs": "",
 263//             }),
 264//         )
 265//         .await;
 266//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
 267//     let project_id = active_call_a
 268//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 269//         .await
 270//         .unwrap();
 271//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
 272
 273//     // Open a file in an editor as the guest.
 274//     let buffer_b = project_b
 275//         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
 276//         .await
 277//         .unwrap();
 278//     let window_b = cx_b.add_window(|_| EmptyView);
 279//     let editor_b = window_b.add_view(cx_b, |cx| {
 280//         Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
 281//     });
 282
 283//     let fake_language_server = fake_language_servers.next().await.unwrap();
 284//     cx_a.foreground().run_until_parked();
 285
 286//     buffer_b.read_with(cx_b, |buffer, _| {
 287//         assert!(!buffer.completion_triggers().is_empty())
 288//     });
 289
 290//     // Type a completion trigger character as the guest.
 291//     editor_b.update(cx_b, |editor, cx| {
 292//         editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
 293//         editor.handle_input(".", cx);
 294//         cx.focus(&editor_b);
 295//     });
 296
 297//     // Receive a completion request as the host's language server.
 298//     // Return some completions from the host's language server.
 299//     cx_a.foreground().start_waiting();
 300//     fake_language_server
 301//         .handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
 302//             assert_eq!(
 303//                 params.text_document_position.text_document.uri,
 304//                 lsp::Url::from_file_path("/a/main.rs").unwrap(),
 305//             );
 306//             assert_eq!(
 307//                 params.text_document_position.position,
 308//                 lsp::Position::new(0, 14),
 309//             );
 310
 311//             Ok(Some(lsp::CompletionResponse::Array(vec![
 312//                 lsp::CompletionItem {
 313//                     label: "first_method(…)".into(),
 314//                     detail: Some("fn(&mut self, B) -> C".into()),
 315//                     text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 316//                         new_text: "first_method($1)".to_string(),
 317//                         range: lsp::Range::new(
 318//                             lsp::Position::new(0, 14),
 319//                             lsp::Position::new(0, 14),
 320//                         ),
 321//                     })),
 322//                     insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 323//                     ..Default::default()
 324//                 },
 325//                 lsp::CompletionItem {
 326//                     label: "second_method(…)".into(),
 327//                     detail: Some("fn(&mut self, C) -> D<E>".into()),
 328//                     text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 329//                         new_text: "second_method()".to_string(),
 330//                         range: lsp::Range::new(
 331//                             lsp::Position::new(0, 14),
 332//                             lsp::Position::new(0, 14),
 333//                         ),
 334//                     })),
 335//                     insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 336//                     ..Default::default()
 337//                 },
 338//             ])))
 339//         })
 340//         .next()
 341//         .await
 342//         .unwrap();
 343//     cx_a.foreground().finish_waiting();
 344
 345//     // Open the buffer on the host.
 346//     let buffer_a = project_a
 347//         .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
 348//         .await
 349//         .unwrap();
 350//     cx_a.foreground().run_until_parked();
 351
 352//     buffer_a.read_with(cx_a, |buffer, _| {
 353//         assert_eq!(buffer.text(), "fn main() { a. }")
 354//     });
 355
 356//     // Confirm a completion on the guest.
 357
 358//     editor_b.read_with(cx_b, |editor, _| assert!(editor.context_menu_visible()));
 359//     editor_b.update(cx_b, |editor, cx| {
 360//         editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
 361//         assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
 362//     });
 363
 364//     // Return a resolved completion from the host's language server.
 365//     // The resolved completion has an additional text edit.
 366//     fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
 367//         |params, _| async move {
 368//             assert_eq!(params.label, "first_method(…)");
 369//             Ok(lsp::CompletionItem {
 370//                 label: "first_method(…)".into(),
 371//                 detail: Some("fn(&mut self, B) -> C".into()),
 372//                 text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
 373//                     new_text: "first_method($1)".to_string(),
 374//                     range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
 375//                 })),
 376//                 additional_text_edits: Some(vec![lsp::TextEdit {
 377//                     new_text: "use d::SomeTrait;\n".to_string(),
 378//                     range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 379//                 }]),
 380//                 insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
 381//                 ..Default::default()
 382//             })
 383//         },
 384//     );
 385
 386//     // The additional edit is applied.
 387//     cx_a.foreground().run_until_parked();
 388
 389//     buffer_a.read_with(cx_a, |buffer, _| {
 390//         assert_eq!(
 391//             buffer.text(),
 392//             "use d::SomeTrait;\nfn main() { a.first_method() }"
 393//         );
 394//     });
 395
 396//     buffer_b.read_with(cx_b, |buffer, _| {
 397//         assert_eq!(
 398//             buffer.text(),
 399//             "use d::SomeTrait;\nfn main() { a.first_method() }"
 400//         );
 401//     });
 402// }
 403//todo!(editor)
 404// #[gpui::test(iterations = 10)]
 405// async fn test_collaborating_with_code_actions(
 406//     executor: BackgroundExecutor,
 407//     cx_a: &mut TestAppContext,
 408//     cx_b: &mut TestAppContext,
 409// ) {
 410//     let mut server = TestServer::start(&executor).await;
 411//     let client_a = server.create_client(cx_a, "user_a").await;
 412//     //
 413//     let client_b = server.create_client(cx_b, "user_b").await;
 414//     server
 415//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 416//         .await;
 417//     let active_call_a = cx_a.read(ActiveCall::global);
 418
 419//     cx_b.update(editor::init);
 420
 421//     // Set up a fake language server.
 422//     let mut language = Language::new(
 423//         LanguageConfig {
 424//             name: "Rust".into(),
 425//             path_suffixes: vec!["rs".to_string()],
 426//             ..Default::default()
 427//         },
 428//         Some(tree_sitter_rust::language()),
 429//     );
 430//     let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
 431//     client_a.language_registry().add(Arc::new(language));
 432
 433//     client_a
 434//         .fs()
 435//         .insert_tree(
 436//             "/a",
 437//             json!({
 438//                 "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
 439//                 "other.rs": "pub fn foo() -> usize { 4 }",
 440//             }),
 441//         )
 442//         .await;
 443//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
 444//     let project_id = active_call_a
 445//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 446//         .await
 447//         .unwrap();
 448
 449//     // Join the project as client B.
 450//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
 451//     let window_b =
 452//         cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
 453//     let workspace_b = window_b.root(cx_b);
 454//     let editor_b = workspace_b
 455//         .update(cx_b, |workspace, cx| {
 456//             workspace.open_path((worktree_id, "main.rs"), None, true, cx)
 457//         })
 458//         .await
 459//         .unwrap()
 460//         .downcast::<Editor>()
 461//         .unwrap();
 462
 463//     let mut fake_language_server = fake_language_servers.next().await.unwrap();
 464//     let mut requests = fake_language_server
 465//         .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
 466//             assert_eq!(
 467//                 params.text_document.uri,
 468//                 lsp::Url::from_file_path("/a/main.rs").unwrap(),
 469//             );
 470//             assert_eq!(params.range.start, lsp::Position::new(0, 0));
 471//             assert_eq!(params.range.end, lsp::Position::new(0, 0));
 472//             Ok(None)
 473//         });
 474//     executor.advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
 475//     requests.next().await;
 476
 477//     // Move cursor to a location that contains code actions.
 478//     editor_b.update(cx_b, |editor, cx| {
 479//         editor.change_selections(None, cx, |s| {
 480//             s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
 481//         });
 482//         cx.focus(&editor_b);
 483//     });
 484
 485//     let mut requests = fake_language_server
 486//         .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
 487//             assert_eq!(
 488//                 params.text_document.uri,
 489//                 lsp::Url::from_file_path("/a/main.rs").unwrap(),
 490//             );
 491//             assert_eq!(params.range.start, lsp::Position::new(1, 31));
 492//             assert_eq!(params.range.end, lsp::Position::new(1, 31));
 493
 494//             Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
 495//                 lsp::CodeAction {
 496//                     title: "Inline into all callers".to_string(),
 497//                     edit: Some(lsp::WorkspaceEdit {
 498//                         changes: Some(
 499//                             [
 500//                                 (
 501//                                     lsp::Url::from_file_path("/a/main.rs").unwrap(),
 502//                                     vec![lsp::TextEdit::new(
 503//                                         lsp::Range::new(
 504//                                             lsp::Position::new(1, 22),
 505//                                             lsp::Position::new(1, 34),
 506//                                         ),
 507//                                         "4".to_string(),
 508//                                     )],
 509//                                 ),
 510//                                 (
 511//                                     lsp::Url::from_file_path("/a/other.rs").unwrap(),
 512//                                     vec![lsp::TextEdit::new(
 513//                                         lsp::Range::new(
 514//                                             lsp::Position::new(0, 0),
 515//                                             lsp::Position::new(0, 27),
 516//                                         ),
 517//                                         "".to_string(),
 518//                                     )],
 519//                                 ),
 520//                             ]
 521//                             .into_iter()
 522//                             .collect(),
 523//                         ),
 524//                         ..Default::default()
 525//                     }),
 526//                     data: Some(json!({
 527//                         "codeActionParams": {
 528//                             "range": {
 529//                                 "start": {"line": 1, "column": 31},
 530//                                 "end": {"line": 1, "column": 31},
 531//                             }
 532//                         }
 533//                     })),
 534//                     ..Default::default()
 535//                 },
 536//             )]))
 537//         });
 538//     executor.advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
 539//     requests.next().await;
 540
 541//     // Toggle code actions and wait for them to display.
 542//     editor_b.update(cx_b, |editor, cx| {
 543//         editor.toggle_code_actions(
 544//             &ToggleCodeActions {
 545//                 deployed_from_indicator: false,
 546//             },
 547//             cx,
 548//         );
 549//     });
 550//     cx_a.foreground().run_until_parked();
 551
 552//     editor_b.read_with(cx_b, |editor, _| assert!(editor.context_menu_visible()));
 553
 554//     fake_language_server.remove_request_handler::<lsp::request::CodeActionRequest>();
 555
 556//     // Confirming the code action will trigger a resolve request.
 557//     let confirm_action = workspace_b
 558//         .update(cx_b, |workspace, cx| {
 559//             Editor::confirm_code_action(workspace, &ConfirmCodeAction { item_ix: Some(0) }, cx)
 560//         })
 561//         .unwrap();
 562//     fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
 563//         |_, _| async move {
 564//             Ok(lsp::CodeAction {
 565//                 title: "Inline into all callers".to_string(),
 566//                 edit: Some(lsp::WorkspaceEdit {
 567//                     changes: Some(
 568//                         [
 569//                             (
 570//                                 lsp::Url::from_file_path("/a/main.rs").unwrap(),
 571//                                 vec![lsp::TextEdit::new(
 572//                                     lsp::Range::new(
 573//                                         lsp::Position::new(1, 22),
 574//                                         lsp::Position::new(1, 34),
 575//                                     ),
 576//                                     "4".to_string(),
 577//                                 )],
 578//                             ),
 579//                             (
 580//                                 lsp::Url::from_file_path("/a/other.rs").unwrap(),
 581//                                 vec![lsp::TextEdit::new(
 582//                                     lsp::Range::new(
 583//                                         lsp::Position::new(0, 0),
 584//                                         lsp::Position::new(0, 27),
 585//                                     ),
 586//                                     "".to_string(),
 587//                                 )],
 588//                             ),
 589//                         ]
 590//                         .into_iter()
 591//                         .collect(),
 592//                     ),
 593//                     ..Default::default()
 594//                 }),
 595//                 ..Default::default()
 596//             })
 597//         },
 598//     );
 599
 600//     // After the action is confirmed, an editor containing both modified files is opened.
 601//     confirm_action.await.unwrap();
 602
 603//     let code_action_editor = workspace_b.read_with(cx_b, |workspace, cx| {
 604//         workspace
 605//             .active_item(cx)
 606//             .unwrap()
 607//             .downcast::<Editor>()
 608//             .unwrap()
 609//     });
 610//     code_action_editor.update(cx_b, |editor, cx| {
 611//         assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
 612//         editor.undo(&Undo, cx);
 613//         assert_eq!(
 614//             editor.text(cx),
 615//             "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
 616//         );
 617//         editor.redo(&Redo, cx);
 618//         assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
 619//     });
 620// }
 621
 622//todo!(editor)
 623// #[gpui::test(iterations = 10)]
 624// async fn test_collaborating_with_renames(
 625//     executor: BackgroundExecutor,
 626//     cx_a: &mut TestAppContext,
 627//     cx_b: &mut TestAppContext,
 628// ) {
 629//     let mut server = TestServer::start(&executor).await;
 630//     let client_a = server.create_client(cx_a, "user_a").await;
 631//     let client_b = server.create_client(cx_b, "user_b").await;
 632//     server
 633//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 634//         .await;
 635//     let active_call_a = cx_a.read(ActiveCall::global);
 636
 637//     cx_b.update(editor::init);
 638
 639//     // Set up a fake language server.
 640//     let mut language = Language::new(
 641//         LanguageConfig {
 642//             name: "Rust".into(),
 643//             path_suffixes: vec!["rs".to_string()],
 644//             ..Default::default()
 645//         },
 646//         Some(tree_sitter_rust::language()),
 647//     );
 648//     let mut fake_language_servers = language
 649//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
 650//             capabilities: lsp::ServerCapabilities {
 651//                 rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
 652//                     prepare_provider: Some(true),
 653//                     work_done_progress_options: Default::default(),
 654//                 })),
 655//                 ..Default::default()
 656//             },
 657//             ..Default::default()
 658//         }))
 659//         .await;
 660//     client_a.language_registry().add(Arc::new(language));
 661
 662//     client_a
 663//         .fs()
 664//         .insert_tree(
 665//             "/dir",
 666//             json!({
 667//                 "one.rs": "const ONE: usize = 1;",
 668//                 "two.rs": "const TWO: usize = one::ONE + one::ONE;"
 669//             }),
 670//         )
 671//         .await;
 672//     let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
 673//     let project_id = active_call_a
 674//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 675//         .await
 676//         .unwrap();
 677//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
 678
 679//     let window_b =
 680//         cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
 681//     let workspace_b = window_b.root(cx_b);
 682//     let editor_b = workspace_b
 683//         .update(cx_b, |workspace, cx| {
 684//             workspace.open_path((worktree_id, "one.rs"), None, true, cx)
 685//         })
 686//         .await
 687//         .unwrap()
 688//         .downcast::<Editor>()
 689//         .unwrap();
 690//     let fake_language_server = fake_language_servers.next().await.unwrap();
 691
 692//     // Move cursor to a location that can be renamed.
 693//     let prepare_rename = editor_b.update(cx_b, |editor, cx| {
 694//         editor.change_selections(None, cx, |s| s.select_ranges([7..7]));
 695//         editor.rename(&Rename, cx).unwrap()
 696//     });
 697
 698//     fake_language_server
 699//         .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
 700//             assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
 701//             assert_eq!(params.position, lsp::Position::new(0, 7));
 702//             Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
 703//                 lsp::Position::new(0, 6),
 704//                 lsp::Position::new(0, 9),
 705//             ))))
 706//         })
 707//         .next()
 708//         .await
 709//         .unwrap();
 710//     prepare_rename.await.unwrap();
 711//     editor_b.update(cx_b, |editor, cx| {
 712//         use editor::ToOffset;
 713//         let rename = editor.pending_rename().unwrap();
 714//         let buffer = editor.buffer().read(cx).snapshot(cx);
 715//         assert_eq!(
 716//             rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
 717//             6..9
 718//         );
 719//         rename.editor.update(cx, |rename_editor, cx| {
 720//             rename_editor.buffer().update(cx, |rename_buffer, cx| {
 721//                 rename_buffer.edit([(0..3, "THREE")], None, cx);
 722//             });
 723//         });
 724//     });
 725
 726//     let confirm_rename = workspace_b.update(cx_b, |workspace, cx| {
 727//         Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap()
 728//     });
 729//     fake_language_server
 730//         .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
 731//             assert_eq!(
 732//                 params.text_document_position.text_document.uri.as_str(),
 733//                 "file:///dir/one.rs"
 734//             );
 735//             assert_eq!(
 736//                 params.text_document_position.position,
 737//                 lsp::Position::new(0, 6)
 738//             );
 739//             assert_eq!(params.new_name, "THREE");
 740//             Ok(Some(lsp::WorkspaceEdit {
 741//                 changes: Some(
 742//                     [
 743//                         (
 744//                             lsp::Url::from_file_path("/dir/one.rs").unwrap(),
 745//                             vec![lsp::TextEdit::new(
 746//                                 lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
 747//                                 "THREE".to_string(),
 748//                             )],
 749//                         ),
 750//                         (
 751//                             lsp::Url::from_file_path("/dir/two.rs").unwrap(),
 752//                             vec![
 753//                                 lsp::TextEdit::new(
 754//                                     lsp::Range::new(
 755//                                         lsp::Position::new(0, 24),
 756//                                         lsp::Position::new(0, 27),
 757//                                     ),
 758//                                     "THREE".to_string(),
 759//                                 ),
 760//                                 lsp::TextEdit::new(
 761//                                     lsp::Range::new(
 762//                                         lsp::Position::new(0, 35),
 763//                                         lsp::Position::new(0, 38),
 764//                                     ),
 765//                                     "THREE".to_string(),
 766//                                 ),
 767//                             ],
 768//                         ),
 769//                     ]
 770//                     .into_iter()
 771//                     .collect(),
 772//                 ),
 773//                 ..Default::default()
 774//             }))
 775//         })
 776//         .next()
 777//         .await
 778//         .unwrap();
 779//     confirm_rename.await.unwrap();
 780
 781//     let rename_editor = workspace_b.read_with(cx_b, |workspace, cx| {
 782//         workspace
 783//             .active_item(cx)
 784//             .unwrap()
 785//             .downcast::<Editor>()
 786//             .unwrap()
 787//     });
 788//     rename_editor.update(cx_b, |editor, cx| {
 789//         assert_eq!(
 790//             editor.text(cx),
 791//             "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
 792//         );
 793//         editor.undo(&Undo, cx);
 794//         assert_eq!(
 795//             editor.text(cx),
 796//             "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
 797//         );
 798//         editor.redo(&Redo, cx);
 799//         assert_eq!(
 800//             editor.text(cx),
 801//             "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
 802//         );
 803//     });
 804
 805//     // Ensure temporary rename edits cannot be undone/redone.
 806//     editor_b.update(cx_b, |editor, cx| {
 807//         editor.undo(&Undo, cx);
 808//         assert_eq!(editor.text(cx), "const ONE: usize = 1;");
 809//         editor.undo(&Undo, cx);
 810//         assert_eq!(editor.text(cx), "const ONE: usize = 1;");
 811//         editor.redo(&Redo, cx);
 812//         assert_eq!(editor.text(cx), "const THREE: usize = 1;");
 813//     })
 814// }
 815
 816//todo!(editor)
 817// #[gpui::test(iterations = 10)]
 818// async fn test_language_server_statuses(
 819//     executor: BackgroundExecutor,
 820//     cx_a: &mut TestAppContext,
 821//     cx_b: &mut TestAppContext,
 822// ) {
 823//     let mut server = TestServer::start(&executor).await;
 824//     let client_a = server.create_client(cx_a, "user_a").await;
 825//     let client_b = server.create_client(cx_b, "user_b").await;
 826//     server
 827//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
 828//         .await;
 829//     let active_call_a = cx_a.read(ActiveCall::global);
 830
 831//     cx_b.update(editor::init);
 832
 833//     // Set up a fake language server.
 834//     let mut language = Language::new(
 835//         LanguageConfig {
 836//             name: "Rust".into(),
 837//             path_suffixes: vec!["rs".to_string()],
 838//             ..Default::default()
 839//         },
 840//         Some(tree_sitter_rust::language()),
 841//     );
 842//     let mut fake_language_servers = language
 843//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
 844//             name: "the-language-server",
 845//             ..Default::default()
 846//         }))
 847//         .await;
 848//     client_a.language_registry().add(Arc::new(language));
 849
 850//     client_a
 851//         .fs()
 852//         .insert_tree(
 853//             "/dir",
 854//             json!({
 855//                 "main.rs": "const ONE: usize = 1;",
 856//             }),
 857//         )
 858//         .await;
 859//     let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
 860
 861//     let _buffer_a = project_a
 862//         .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
 863//         .await
 864//         .unwrap();
 865
 866//     let fake_language_server = fake_language_servers.next().await.unwrap();
 867//     fake_language_server.start_progress("the-token").await;
 868//     fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
 869//         token: lsp::NumberOrString::String("the-token".to_string()),
 870//         value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
 871//             lsp::WorkDoneProgressReport {
 872//                 message: Some("the-message".to_string()),
 873//                 ..Default::default()
 874//             },
 875//         )),
 876//     });
 877//     executor.run_until_parked();
 878
 879//     project_a.read_with(cx_a, |project, _| {
 880//         let status = project.language_server_statuses().next().unwrap();
 881//         assert_eq!(status.name, "the-language-server");
 882//         assert_eq!(status.pending_work.len(), 1);
 883//         assert_eq!(
 884//             status.pending_work["the-token"].message.as_ref().unwrap(),
 885//             "the-message"
 886//         );
 887//     });
 888
 889//     let project_id = active_call_a
 890//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
 891//         .await
 892//         .unwrap();
 893//     executor.run_until_parked();
 894//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
 895
 896//     project_b.read_with(cx_b, |project, _| {
 897//         let status = project.language_server_statuses().next().unwrap();
 898//         assert_eq!(status.name, "the-language-server");
 899//     });
 900
 901//     fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
 902//         token: lsp::NumberOrString::String("the-token".to_string()),
 903//         value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
 904//             lsp::WorkDoneProgressReport {
 905//                 message: Some("the-message-2".to_string()),
 906//                 ..Default::default()
 907//             },
 908//         )),
 909//     });
 910//     executor.run_until_parked();
 911
 912//     project_a.read_with(cx_a, |project, _| {
 913//         let status = project.language_server_statuses().next().unwrap();
 914//         assert_eq!(status.name, "the-language-server");
 915//         assert_eq!(status.pending_work.len(), 1);
 916//         assert_eq!(
 917//             status.pending_work["the-token"].message.as_ref().unwrap(),
 918//             "the-message-2"
 919//         );
 920//     });
 921
 922//     project_b.read_with(cx_b, |project, _| {
 923//         let status = project.language_server_statuses().next().unwrap();
 924//         assert_eq!(status.name, "the-language-server");
 925//         assert_eq!(status.pending_work.len(), 1);
 926//         assert_eq!(
 927//             status.pending_work["the-token"].message.as_ref().unwrap(),
 928//             "the-message-2"
 929//         );
 930//     });
 931// }
 932
 933// #[gpui::test(iterations = 10)]
 934// async fn test_share_project(
 935//     executor: BackgroundExecutor,
 936//     cx_a: &mut TestAppContext,
 937//     cx_b: &mut TestAppContext,
 938//     cx_c: &mut TestAppContext,
 939// ) {
 940//     let window_b = cx_b.add_window(|_| EmptyView);
 941//     let mut server = TestServer::start(&executor).await;
 942//     let client_a = server.create_client(cx_a, "user_a").await;
 943//     let client_b = server.create_client(cx_b, "user_b").await;
 944//     let client_c = server.create_client(cx_c, "user_c").await;
 945//     server
 946//         .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
 947//         .await;
 948//     let active_call_a = cx_a.read(ActiveCall::global);
 949//     let active_call_b = cx_b.read(ActiveCall::global);
 950//     let active_call_c = cx_c.read(ActiveCall::global);
 951
 952//     client_a
 953//         .fs()
 954//         .insert_tree(
 955//             "/a",
 956//             json!({
 957//                 ".gitignore": "ignored-dir",
 958//                 "a.txt": "a-contents",
 959//                 "b.txt": "b-contents",
 960//                 "ignored-dir": {
 961//                     "c.txt": "",
 962//                     "d.txt": "",
 963//                 }
 964//             }),
 965//         )
 966//         .await;
 967
 968//     // Invite client B to collaborate on a project
 969//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
 970//     active_call_a
 971//         .update(cx_a, |call, cx| {
 972//             call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
 973//         })
 974//         .await
 975//         .unwrap();
 976
 977//     // Join that project as client B
 978
 979//     let incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
 980//     executor.run_until_parked();
 981//     let call = incoming_call_b.borrow().clone().unwrap();
 982//     assert_eq!(call.calling_user.github_login, "user_a");
 983//     let initial_project = call.initial_project.unwrap();
 984//     active_call_b
 985//         .update(cx_b, |call, cx| call.accept_incoming(cx))
 986//         .await
 987//         .unwrap();
 988//     let client_b_peer_id = client_b.peer_id().unwrap();
 989//     let project_b = client_b
 990//         .build_remote_project(initial_project.id, cx_b)
 991//         .await;
 992
 993//     let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
 994
 995//     executor.run_until_parked();
 996
 997//     project_a.read_with(cx_a, |project, _| {
 998//         let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
 999//         assert_eq!(client_b_collaborator.replica_id, replica_id_b);
1000//     });
1001
1002//     project_b.read_with(cx_b, |project, cx| {
1003//         let worktree = project.worktrees().next().unwrap().read(cx);
1004//         assert_eq!(
1005//             worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
1006//             [
1007//                 Path::new(".gitignore"),
1008//                 Path::new("a.txt"),
1009//                 Path::new("b.txt"),
1010//                 Path::new("ignored-dir"),
1011//             ]
1012//         );
1013//     });
1014
1015//     project_b
1016//         .update(cx_b, |project, cx| {
1017//             let worktree = project.worktrees().next().unwrap();
1018//             let entry = worktree.read(cx).entry_for_path("ignored-dir").unwrap();
1019//             project.expand_entry(worktree_id, entry.id, cx).unwrap()
1020//         })
1021//         .await
1022//         .unwrap();
1023
1024//     project_b.read_with(cx_b, |project, cx| {
1025//         let worktree = project.worktrees().next().unwrap().read(cx);
1026//         assert_eq!(
1027//             worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
1028//             [
1029//                 Path::new(".gitignore"),
1030//                 Path::new("a.txt"),
1031//                 Path::new("b.txt"),
1032//                 Path::new("ignored-dir"),
1033//                 Path::new("ignored-dir/c.txt"),
1034//                 Path::new("ignored-dir/d.txt"),
1035//             ]
1036//         );
1037//     });
1038
1039//     // Open the same file as client B and client A.
1040//     let buffer_b = project_b
1041//         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
1042//         .await
1043//         .unwrap();
1044
1045//     buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
1046
1047//     project_a.read_with(cx_a, |project, cx| {
1048//         assert!(project.has_open_buffer((worktree_id, "b.txt"), cx))
1049//     });
1050//     let buffer_a = project_a
1051//         .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
1052//         .await
1053//         .unwrap();
1054
1055//     let editor_b = window_b.add_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
1056
1057//     // Client A sees client B's selection
1058//     executor.run_until_parked();
1059
1060//     buffer_a.read_with(cx_a, |buffer, _| {
1061//         buffer
1062//             .snapshot()
1063//             .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
1064//             .count()
1065//             == 1
1066//     });
1067
1068//     // Edit the buffer as client B and see that edit as client A.
1069//     editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx));
1070//     executor.run_until_parked();
1071
1072//     buffer_a.read_with(cx_a, |buffer, _| {
1073//         assert_eq!(buffer.text(), "ok, b-contents")
1074//     });
1075
1076//     // Client B can invite client C on a project shared by client A.
1077//     active_call_b
1078//         .update(cx_b, |call, cx| {
1079//             call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
1080//         })
1081//         .await
1082//         .unwrap();
1083
1084//     let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
1085//     executor.run_until_parked();
1086//     let call = incoming_call_c.borrow().clone().unwrap();
1087//     assert_eq!(call.calling_user.github_login, "user_b");
1088//     let initial_project = call.initial_project.unwrap();
1089//     active_call_c
1090//         .update(cx_c, |call, cx| call.accept_incoming(cx))
1091//         .await
1092//         .unwrap();
1093//     let _project_c = client_c
1094//         .build_remote_project(initial_project.id, cx_c)
1095//         .await;
1096
1097//     // Client B closes the editor, and client A sees client B's selections removed.
1098//     cx_b.update(move |_| drop(editor_b));
1099//     executor.run_until_parked();
1100
1101//     buffer_a.read_with(cx_a, |buffer, _| {
1102//         buffer
1103//             .snapshot()
1104//             .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
1105//             .count()
1106//             == 0
1107//     });
1108// }