1//todo(partially ported)
2// use std::{
3// path::Path,
4// sync::{
5// atomic::{self, AtomicBool, AtomicUsize},
6// Arc,
7// },
8// };
9
10// use call::ActiveCall;
11// use editor::{
12// test::editor_test_context::{AssertionContextManager, EditorTestContext},
13// Anchor, ConfirmCodeAction, ConfirmCompletion, ConfirmRename, Editor, Redo, Rename,
14// ToggleCodeActions, Undo,
15// };
16// use gpui::{BackgroundExecutor, TestAppContext, VisualContext, VisualTestContext};
17// use indoc::indoc;
18// use language::{
19// language_settings::{AllLanguageSettings, InlayHintSettings},
20// tree_sitter_rust, FakeLspAdapter, Language, LanguageConfig,
21// };
22// use rpc::RECEIVE_TIMEOUT;
23// use serde_json::json;
24// use settings::SettingsStore;
25// use text::Point;
26// use workspace::Workspace;
27
28// use crate::{rpc::RECONNECT_TIMEOUT, tests::TestServer};
29
30// #[gpui::test(iterations = 10)]
31// async fn test_host_disconnect(
32// executor: BackgroundExecutor,
33// cx_a: &mut TestAppContext,
34// cx_b: &mut TestAppContext,
35// cx_c: &mut TestAppContext,
36// ) {
37// let mut server = TestServer::start(executor).await;
38// let client_a = server.create_client(cx_a, "user_a").await;
39// let client_b = server.create_client(cx_b, "user_b").await;
40// let client_c = server.create_client(cx_c, "user_c").await;
41// server
42// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
43// .await;
44
45// cx_b.update(editor::init);
46
47// client_a
48// .fs()
49// .insert_tree(
50// "/a",
51// serde_json::json!({
52// "a.txt": "a-contents",
53// "b.txt": "b-contents",
54// }),
55// )
56// .await;
57
58// let active_call_a = cx_a.read(ActiveCall::global);
59// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
60
61// let worktree_a = project_a.read_with(cx_a, |project, cx| project.worktrees().next().unwrap());
62// let project_id = active_call_a
63// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
64// .await
65// .unwrap();
66
67// let project_b = client_b.build_remote_project(project_id, cx_b).await;
68// executor.run_until_parked();
69
70// assert!(worktree_a.read_with(cx_a, |tree, _| tree.as_local().unwrap().is_shared()));
71
72// let workspace_b =
73// cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
74// let cx_b = &mut VisualTestContext::from_window(*workspace_b, cx_b);
75
76// let editor_b = workspace_b
77// .update(cx_b, |workspace, cx| {
78// workspace.open_path((worktree_id, "b.txt"), None, true, cx)
79// })
80// .unwrap()
81// .await
82// .unwrap()
83// .downcast::<Editor>()
84// .unwrap();
85
86// //TODO: focus
87// assert!(cx_b.update_view(&editor_b, |editor, cx| editor.is_focused(cx)));
88// editor_b.update(cx_b, |editor, cx| editor.insert("X", cx));
89// //todo(is_edited)
90// // assert!(workspace_b.is_edited(cx_b));
91
92// // Drop client A's connection. Collaborators should disappear and the project should not be shown as shared.
93// server.forbid_connections();
94// server.disconnect_client(client_a.peer_id().unwrap());
95// executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
96
97// project_a.read_with(cx_a, |project, _| project.collaborators().is_empty());
98
99// project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
100
101// project_b.read_with(cx_b, |project, _| project.is_read_only());
102
103// assert!(worktree_a.read_with(cx_a, |tree, _| !tree.as_local().unwrap().is_shared()));
104
105// // Ensure client B's edited state is reset and that the whole window is blurred.
106
107// workspace_b.update(cx_b, |_, cx| {
108// assert_eq!(cx.focused_view_id(), None);
109// });
110// // assert!(!workspace_b.is_edited(cx_b));
111
112// // Ensure client B is not prompted to save edits when closing window after disconnecting.
113// let can_close = workspace_b
114// .update(cx_b, |workspace, cx| workspace.prepare_to_close(true, cx))
115// .await
116// .unwrap();
117// assert!(can_close);
118
119// // Allow client A to reconnect to the server.
120// server.allow_connections();
121// executor.advance_clock(RECEIVE_TIMEOUT);
122
123// // Client B calls client A again after they reconnected.
124// let active_call_b = cx_b.read(ActiveCall::global);
125// active_call_b
126// .update(cx_b, |call, cx| {
127// call.invite(client_a.user_id().unwrap(), None, cx)
128// })
129// .await
130// .unwrap();
131// executor.run_until_parked();
132// active_call_a
133// .update(cx_a, |call, cx| call.accept_incoming(cx))
134// .await
135// .unwrap();
136
137// active_call_a
138// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
139// .await
140// .unwrap();
141
142// // Drop client A's connection again. We should still unshare it successfully.
143// server.forbid_connections();
144// server.disconnect_client(client_a.peer_id().unwrap());
145// executor.advance_clock(RECEIVE_TIMEOUT + RECONNECT_TIMEOUT);
146
147// project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
148// }
149
150// #[gpui::test]
151// async fn test_newline_above_or_below_does_not_move_guest_cursor(
152// executor: BackgroundExecutor,
153// cx_a: &mut TestAppContext,
154// cx_b: &mut TestAppContext,
155// ) {
156// let mut server = TestServer::start(&executor).await;
157// let client_a = server.create_client(cx_a, "user_a").await;
158// let client_b = server.create_client(cx_b, "user_b").await;
159// server
160// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
161// .await;
162// let active_call_a = cx_a.read(ActiveCall::global);
163
164// client_a
165// .fs()
166// .insert_tree("/dir", json!({ "a.txt": "Some text\n" }))
167// .await;
168// let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
169// let project_id = active_call_a
170// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
171// .await
172// .unwrap();
173
174// let project_b = client_b.build_remote_project(project_id, cx_b).await;
175
176// // Open a buffer as client A
177// let buffer_a = project_a
178// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
179// .await
180// .unwrap();
181// let window_a = cx_a.add_empty_window();
182// let editor_a =
183// window_a.build_view(cx_a, |cx| Editor::for_buffer(buffer_a, Some(project_a), cx));
184// let mut editor_cx_a = EditorTestContext {
185// cx: cx_a,
186// window: window_a.into(),
187// editor: editor_a,
188// assertion_cx: AssertionContextManager::new(),
189// };
190
191// // Open a buffer as client B
192// let buffer_b = project_b
193// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "a.txt"), cx))
194// .await
195// .unwrap();
196// let window_b = cx_b.add_empty_window();
197// let editor_b =
198// window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, Some(project_b), cx));
199// let mut editor_cx_b = EditorTestContext {
200// cx: cx_b,
201// window: window_b.into(),
202// editor: editor_b,
203// assertion_cx: AssertionContextManager::new(),
204// };
205
206// // Test newline above
207// editor_cx_a.set_selections_state(indoc! {"
208// Some textˇ
209// "});
210// editor_cx_b.set_selections_state(indoc! {"
211// Some textˇ
212// "});
213// editor_cx_a.update_editor(|editor, cx| editor.newline_above(&editor::NewlineAbove, cx));
214// executor.run_until_parked();
215// editor_cx_a.assert_editor_state(indoc! {"
216// ˇ
217// Some text
218// "});
219// editor_cx_b.assert_editor_state(indoc! {"
220
221// Some textˇ
222// "});
223
224// // Test newline below
225// editor_cx_a.set_selections_state(indoc! {"
226
227// Some textˇ
228// "});
229// editor_cx_b.set_selections_state(indoc! {"
230
231// Some textˇ
232// "});
233// editor_cx_a.update_editor(|editor, cx| editor.newline_below(&editor::NewlineBelow, cx));
234// executor.run_until_parked();
235// editor_cx_a.assert_editor_state(indoc! {"
236
237// Some text
238// ˇ
239// "});
240// editor_cx_b.assert_editor_state(indoc! {"
241
242// Some textˇ
243
244// "});
245// }
246
247// #[gpui::test(iterations = 10)]
248// async fn test_collaborating_with_completion(
249// executor: BackgroundExecutor,
250// cx_a: &mut TestAppContext,
251// cx_b: &mut TestAppContext,
252// ) {
253// let mut server = TestServer::start(&executor).await;
254// let client_a = server.create_client(cx_a, "user_a").await;
255// let client_b = server.create_client(cx_b, "user_b").await;
256// server
257// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
258// .await;
259// let active_call_a = cx_a.read(ActiveCall::global);
260
261// // Set up a fake language server.
262// let mut language = Language::new(
263// LanguageConfig {
264// name: "Rust".into(),
265// path_suffixes: vec!["rs".to_string()],
266// ..Default::default()
267// },
268// Some(tree_sitter_rust::language()),
269// );
270// let mut fake_language_servers = language
271// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
272// capabilities: lsp::ServerCapabilities {
273// completion_provider: Some(lsp::CompletionOptions {
274// trigger_characters: Some(vec![".".to_string()]),
275// resolve_provider: Some(true),
276// ..Default::default()
277// }),
278// ..Default::default()
279// },
280// ..Default::default()
281// }))
282// .await;
283// client_a.language_registry().add(Arc::new(language));
284
285// client_a
286// .fs()
287// .insert_tree(
288// "/a",
289// json!({
290// "main.rs": "fn main() { a }",
291// "other.rs": "",
292// }),
293// )
294// .await;
295// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
296// let project_id = active_call_a
297// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
298// .await
299// .unwrap();
300// let project_b = client_b.build_remote_project(project_id, cx_b).await;
301
302// // Open a file in an editor as the guest.
303// let buffer_b = project_b
304// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
305// .await
306// .unwrap();
307// let window_b = cx_b.add_empty_window();
308// let editor_b = window_b.build_view(cx_b, |cx| {
309// Editor::for_buffer(buffer_b.clone(), Some(project_b.clone()), cx)
310// });
311
312// let fake_language_server = fake_language_servers.next().await.unwrap();
313// cx_a.foreground().run_until_parked();
314
315// buffer_b.read_with(cx_b, |buffer, _| {
316// assert!(!buffer.completion_triggers().is_empty())
317// });
318
319// // Type a completion trigger character as the guest.
320// editor_b.update(cx_b, |editor, cx| {
321// editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
322// editor.handle_input(".", cx);
323// cx.focus(&editor_b);
324// });
325
326// // Receive a completion request as the host's language server.
327// // Return some completions from the host's language server.
328// cx_a.foreground().start_waiting();
329// fake_language_server
330// .handle_request::<lsp::request::Completion, _, _>(|params, _| async move {
331// assert_eq!(
332// params.text_document_position.text_document.uri,
333// lsp::Url::from_file_path("/a/main.rs").unwrap(),
334// );
335// assert_eq!(
336// params.text_document_position.position,
337// lsp::Position::new(0, 14),
338// );
339
340// Ok(Some(lsp::CompletionResponse::Array(vec![
341// lsp::CompletionItem {
342// label: "first_method(…)".into(),
343// detail: Some("fn(&mut self, B) -> C".into()),
344// text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
345// new_text: "first_method($1)".to_string(),
346// range: lsp::Range::new(
347// lsp::Position::new(0, 14),
348// lsp::Position::new(0, 14),
349// ),
350// })),
351// insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
352// ..Default::default()
353// },
354// lsp::CompletionItem {
355// label: "second_method(…)".into(),
356// detail: Some("fn(&mut self, C) -> D<E>".into()),
357// text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
358// new_text: "second_method()".to_string(),
359// range: lsp::Range::new(
360// lsp::Position::new(0, 14),
361// lsp::Position::new(0, 14),
362// ),
363// })),
364// insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
365// ..Default::default()
366// },
367// ])))
368// })
369// .next()
370// .await
371// .unwrap();
372// cx_a.foreground().finish_waiting();
373
374// // Open the buffer on the host.
375// let buffer_a = project_a
376// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
377// .await
378// .unwrap();
379// cx_a.foreground().run_until_parked();
380
381// buffer_a.read_with(cx_a, |buffer, _| {
382// assert_eq!(buffer.text(), "fn main() { a. }")
383// });
384
385// // Confirm a completion on the guest.
386
387// editor_b.read_with(cx_b, |editor, _| assert!(editor.context_menu_visible()));
388// editor_b.update(cx_b, |editor, cx| {
389// editor.confirm_completion(&ConfirmCompletion { item_ix: Some(0) }, cx);
390// assert_eq!(editor.text(cx), "fn main() { a.first_method() }");
391// });
392
393// // Return a resolved completion from the host's language server.
394// // The resolved completion has an additional text edit.
395// fake_language_server.handle_request::<lsp::request::ResolveCompletionItem, _, _>(
396// |params, _| async move {
397// assert_eq!(params.label, "first_method(…)");
398// Ok(lsp::CompletionItem {
399// label: "first_method(…)".into(),
400// detail: Some("fn(&mut self, B) -> C".into()),
401// text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
402// new_text: "first_method($1)".to_string(),
403// range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
404// })),
405// additional_text_edits: Some(vec![lsp::TextEdit {
406// new_text: "use d::SomeTrait;\n".to_string(),
407// range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
408// }]),
409// insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
410// ..Default::default()
411// })
412// },
413// );
414
415// // The additional edit is applied.
416// cx_a.executor().run_until_parked();
417
418// buffer_a.read_with(cx_a, |buffer, _| {
419// assert_eq!(
420// buffer.text(),
421// "use d::SomeTrait;\nfn main() { a.first_method() }"
422// );
423// });
424
425// buffer_b.read_with(cx_b, |buffer, _| {
426// assert_eq!(
427// buffer.text(),
428// "use d::SomeTrait;\nfn main() { a.first_method() }"
429// );
430// });
431// }
432
433// #[gpui::test(iterations = 10)]
434// async fn test_collaborating_with_code_actions(
435// executor: BackgroundExecutor,
436// cx_a: &mut TestAppContext,
437// cx_b: &mut TestAppContext,
438// ) {
439// let mut server = TestServer::start(&executor).await;
440// let client_a = server.create_client(cx_a, "user_a").await;
441// //
442// let client_b = server.create_client(cx_b, "user_b").await;
443// server
444// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
445// .await;
446// let active_call_a = cx_a.read(ActiveCall::global);
447
448// cx_b.update(editor::init);
449
450// // Set up a fake language server.
451// let mut language = Language::new(
452// LanguageConfig {
453// name: "Rust".into(),
454// path_suffixes: vec!["rs".to_string()],
455// ..Default::default()
456// },
457// Some(tree_sitter_rust::language()),
458// );
459// let mut fake_language_servers = language.set_fake_lsp_adapter(Default::default()).await;
460// client_a.language_registry().add(Arc::new(language));
461
462// client_a
463// .fs()
464// .insert_tree(
465// "/a",
466// json!({
467// "main.rs": "mod other;\nfn main() { let foo = other::foo(); }",
468// "other.rs": "pub fn foo() -> usize { 4 }",
469// }),
470// )
471// .await;
472// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
473// let project_id = active_call_a
474// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
475// .await
476// .unwrap();
477
478// // Join the project as client B.
479// let project_b = client_b.build_remote_project(project_id, cx_b).await;
480// let window_b =
481// cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
482// let workspace_b = window_b.root(cx_b);
483// let editor_b = workspace_b
484// .update(cx_b, |workspace, cx| {
485// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
486// })
487// .await
488// .unwrap()
489// .downcast::<Editor>()
490// .unwrap();
491
492// let mut fake_language_server = fake_language_servers.next().await.unwrap();
493// let mut requests = fake_language_server
494// .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
495// assert_eq!(
496// params.text_document.uri,
497// lsp::Url::from_file_path("/a/main.rs").unwrap(),
498// );
499// assert_eq!(params.range.start, lsp::Position::new(0, 0));
500// assert_eq!(params.range.end, lsp::Position::new(0, 0));
501// Ok(None)
502// });
503// executor.advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
504// requests.next().await;
505
506// // Move cursor to a location that contains code actions.
507// editor_b.update(cx_b, |editor, cx| {
508// editor.change_selections(None, cx, |s| {
509// s.select_ranges([Point::new(1, 31)..Point::new(1, 31)])
510// });
511// cx.focus(&editor_b);
512// });
513
514// let mut requests = fake_language_server
515// .handle_request::<lsp::request::CodeActionRequest, _, _>(|params, _| async move {
516// assert_eq!(
517// params.text_document.uri,
518// lsp::Url::from_file_path("/a/main.rs").unwrap(),
519// );
520// assert_eq!(params.range.start, lsp::Position::new(1, 31));
521// assert_eq!(params.range.end, lsp::Position::new(1, 31));
522
523// Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
524// lsp::CodeAction {
525// title: "Inline into all callers".to_string(),
526// edit: Some(lsp::WorkspaceEdit {
527// changes: Some(
528// [
529// (
530// lsp::Url::from_file_path("/a/main.rs").unwrap(),
531// vec![lsp::TextEdit::new(
532// lsp::Range::new(
533// lsp::Position::new(1, 22),
534// lsp::Position::new(1, 34),
535// ),
536// "4".to_string(),
537// )],
538// ),
539// (
540// lsp::Url::from_file_path("/a/other.rs").unwrap(),
541// vec![lsp::TextEdit::new(
542// lsp::Range::new(
543// lsp::Position::new(0, 0),
544// lsp::Position::new(0, 27),
545// ),
546// "".to_string(),
547// )],
548// ),
549// ]
550// .into_iter()
551// .collect(),
552// ),
553// ..Default::default()
554// }),
555// data: Some(json!({
556// "codeActionParams": {
557// "range": {
558// "start": {"line": 1, "column": 31},
559// "end": {"line": 1, "column": 31},
560// }
561// }
562// })),
563// ..Default::default()
564// },
565// )]))
566// });
567// executor.advance_clock(editor::CODE_ACTIONS_DEBOUNCE_TIMEOUT * 2);
568// requests.next().await;
569
570// // Toggle code actions and wait for them to display.
571// editor_b.update(cx_b, |editor, cx| {
572// editor.toggle_code_actions(
573// &ToggleCodeActions {
574// deployed_from_indicator: false,
575// },
576// cx,
577// );
578// });
579// cx_a.foreground().run_until_parked();
580
581// editor_b.read_with(cx_b, |editor, _| assert!(editor.context_menu_visible()));
582
583// fake_language_server.remove_request_handler::<lsp::request::CodeActionRequest>();
584
585// // Confirming the code action will trigger a resolve request.
586// let confirm_action = workspace_b
587// .update(cx_b, |workspace, cx| {
588// Editor::confirm_code_action(workspace, &ConfirmCodeAction { item_ix: Some(0) }, cx)
589// })
590// .unwrap();
591// fake_language_server.handle_request::<lsp::request::CodeActionResolveRequest, _, _>(
592// |_, _| async move {
593// Ok(lsp::CodeAction {
594// title: "Inline into all callers".to_string(),
595// edit: Some(lsp::WorkspaceEdit {
596// changes: Some(
597// [
598// (
599// lsp::Url::from_file_path("/a/main.rs").unwrap(),
600// vec![lsp::TextEdit::new(
601// lsp::Range::new(
602// lsp::Position::new(1, 22),
603// lsp::Position::new(1, 34),
604// ),
605// "4".to_string(),
606// )],
607// ),
608// (
609// lsp::Url::from_file_path("/a/other.rs").unwrap(),
610// vec![lsp::TextEdit::new(
611// lsp::Range::new(
612// lsp::Position::new(0, 0),
613// lsp::Position::new(0, 27),
614// ),
615// "".to_string(),
616// )],
617// ),
618// ]
619// .into_iter()
620// .collect(),
621// ),
622// ..Default::default()
623// }),
624// ..Default::default()
625// })
626// },
627// );
628
629// // After the action is confirmed, an editor containing both modified files is opened.
630// confirm_action.await.unwrap();
631
632// let code_action_editor = workspace_b.read_with(cx_b, |workspace, cx| {
633// workspace
634// .active_item(cx)
635// .unwrap()
636// .downcast::<Editor>()
637// .unwrap()
638// });
639// code_action_editor.update(cx_b, |editor, cx| {
640// assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
641// editor.undo(&Undo, cx);
642// assert_eq!(
643// editor.text(cx),
644// "mod other;\nfn main() { let foo = other::foo(); }\npub fn foo() -> usize { 4 }"
645// );
646// editor.redo(&Redo, cx);
647// assert_eq!(editor.text(cx), "mod other;\nfn main() { let foo = 4; }\n");
648// });
649// }
650
651// #[gpui::test(iterations = 10)]
652// async fn test_collaborating_with_renames(
653// executor: BackgroundExecutor,
654// cx_a: &mut TestAppContext,
655// cx_b: &mut TestAppContext,
656// ) {
657// let mut server = TestServer::start(&executor).await;
658// let client_a = server.create_client(cx_a, "user_a").await;
659// let client_b = server.create_client(cx_b, "user_b").await;
660// server
661// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
662// .await;
663// let active_call_a = cx_a.read(ActiveCall::global);
664
665// cx_b.update(editor::init);
666
667// // Set up a fake language server.
668// let mut language = Language::new(
669// LanguageConfig {
670// name: "Rust".into(),
671// path_suffixes: vec!["rs".to_string()],
672// ..Default::default()
673// },
674// Some(tree_sitter_rust::language()),
675// );
676// let mut fake_language_servers = language
677// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
678// capabilities: lsp::ServerCapabilities {
679// rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
680// prepare_provider: Some(true),
681// work_done_progress_options: Default::default(),
682// })),
683// ..Default::default()
684// },
685// ..Default::default()
686// }))
687// .await;
688// client_a.language_registry().add(Arc::new(language));
689
690// client_a
691// .fs()
692// .insert_tree(
693// "/dir",
694// json!({
695// "one.rs": "const ONE: usize = 1;",
696// "two.rs": "const TWO: usize = one::ONE + one::ONE;"
697// }),
698// )
699// .await;
700// let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
701// let project_id = active_call_a
702// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
703// .await
704// .unwrap();
705// let project_b = client_b.build_remote_project(project_id, cx_b).await;
706
707// let window_b =
708// cx_b.add_window(|cx| Workspace::new(0, project_b.clone(), client_b.app_state.clone(), cx));
709// let workspace_b = window_b.root(cx_b);
710// let editor_b = workspace_b
711// .update(cx_b, |workspace, cx| {
712// workspace.open_path((worktree_id, "one.rs"), None, true, cx)
713// })
714// .await
715// .unwrap()
716// .downcast::<Editor>()
717// .unwrap();
718// let fake_language_server = fake_language_servers.next().await.unwrap();
719
720// // Move cursor to a location that can be renamed.
721// let prepare_rename = editor_b.update(cx_b, |editor, cx| {
722// editor.change_selections(None, cx, |s| s.select_ranges([7..7]));
723// editor.rename(&Rename, cx).unwrap()
724// });
725
726// fake_language_server
727// .handle_request::<lsp::request::PrepareRenameRequest, _, _>(|params, _| async move {
728// assert_eq!(params.text_document.uri.as_str(), "file:///dir/one.rs");
729// assert_eq!(params.position, lsp::Position::new(0, 7));
730// Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range::new(
731// lsp::Position::new(0, 6),
732// lsp::Position::new(0, 9),
733// ))))
734// })
735// .next()
736// .await
737// .unwrap();
738// prepare_rename.await.unwrap();
739// editor_b.update(cx_b, |editor, cx| {
740// use editor::ToOffset;
741// let rename = editor.pending_rename().unwrap();
742// let buffer = editor.buffer().read(cx).snapshot(cx);
743// assert_eq!(
744// rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),
745// 6..9
746// );
747// rename.editor.update(cx, |rename_editor, cx| {
748// rename_editor.buffer().update(cx, |rename_buffer, cx| {
749// rename_buffer.edit([(0..3, "THREE")], None, cx);
750// });
751// });
752// });
753
754// let confirm_rename = workspace_b.update(cx_b, |workspace, cx| {
755// Editor::confirm_rename(workspace, &ConfirmRename, cx).unwrap()
756// });
757// fake_language_server
758// .handle_request::<lsp::request::Rename, _, _>(|params, _| async move {
759// assert_eq!(
760// params.text_document_position.text_document.uri.as_str(),
761// "file:///dir/one.rs"
762// );
763// assert_eq!(
764// params.text_document_position.position,
765// lsp::Position::new(0, 6)
766// );
767// assert_eq!(params.new_name, "THREE");
768// Ok(Some(lsp::WorkspaceEdit {
769// changes: Some(
770// [
771// (
772// lsp::Url::from_file_path("/dir/one.rs").unwrap(),
773// vec![lsp::TextEdit::new(
774// lsp::Range::new(lsp::Position::new(0, 6), lsp::Position::new(0, 9)),
775// "THREE".to_string(),
776// )],
777// ),
778// (
779// lsp::Url::from_file_path("/dir/two.rs").unwrap(),
780// vec![
781// lsp::TextEdit::new(
782// lsp::Range::new(
783// lsp::Position::new(0, 24),
784// lsp::Position::new(0, 27),
785// ),
786// "THREE".to_string(),
787// ),
788// lsp::TextEdit::new(
789// lsp::Range::new(
790// lsp::Position::new(0, 35),
791// lsp::Position::new(0, 38),
792// ),
793// "THREE".to_string(),
794// ),
795// ],
796// ),
797// ]
798// .into_iter()
799// .collect(),
800// ),
801// ..Default::default()
802// }))
803// })
804// .next()
805// .await
806// .unwrap();
807// confirm_rename.await.unwrap();
808
809// let rename_editor = workspace_b.read_with(cx_b, |workspace, cx| {
810// workspace
811// .active_item(cx)
812// .unwrap()
813// .downcast::<Editor>()
814// .unwrap()
815// });
816// rename_editor.update(cx_b, |editor, cx| {
817// assert_eq!(
818// editor.text(cx),
819// "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
820// );
821// editor.undo(&Undo, cx);
822// assert_eq!(
823// editor.text(cx),
824// "const ONE: usize = 1;\nconst TWO: usize = one::ONE + one::ONE;"
825// );
826// editor.redo(&Redo, cx);
827// assert_eq!(
828// editor.text(cx),
829// "const THREE: usize = 1;\nconst TWO: usize = one::THREE + one::THREE;"
830// );
831// });
832
833// // Ensure temporary rename edits cannot be undone/redone.
834// editor_b.update(cx_b, |editor, cx| {
835// editor.undo(&Undo, cx);
836// assert_eq!(editor.text(cx), "const ONE: usize = 1;");
837// editor.undo(&Undo, cx);
838// assert_eq!(editor.text(cx), "const ONE: usize = 1;");
839// editor.redo(&Redo, cx);
840// assert_eq!(editor.text(cx), "const THREE: usize = 1;");
841// })
842// }
843
844// #[gpui::test(iterations = 10)]
845// async fn test_language_server_statuses(
846// executor: BackgroundExecutor,
847// cx_a: &mut TestAppContext,
848// cx_b: &mut TestAppContext,
849// ) {
850// let mut server = TestServer::start(&executor).await;
851// let client_a = server.create_client(cx_a, "user_a").await;
852// let client_b = server.create_client(cx_b, "user_b").await;
853// server
854// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
855// .await;
856// let active_call_a = cx_a.read(ActiveCall::global);
857
858// cx_b.update(editor::init);
859
860// // Set up a fake language server.
861// let mut language = Language::new(
862// LanguageConfig {
863// name: "Rust".into(),
864// path_suffixes: vec!["rs".to_string()],
865// ..Default::default()
866// },
867// Some(tree_sitter_rust::language()),
868// );
869// let mut fake_language_servers = language
870// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
871// name: "the-language-server",
872// ..Default::default()
873// }))
874// .await;
875// client_a.language_registry().add(Arc::new(language));
876
877// client_a
878// .fs()
879// .insert_tree(
880// "/dir",
881// json!({
882// "main.rs": "const ONE: usize = 1;",
883// }),
884// )
885// .await;
886// let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
887
888// let _buffer_a = project_a
889// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
890// .await
891// .unwrap();
892
893// let fake_language_server = fake_language_servers.next().await.unwrap();
894// fake_language_server.start_progress("the-token").await;
895// fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
896// token: lsp::NumberOrString::String("the-token".to_string()),
897// value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
898// lsp::WorkDoneProgressReport {
899// message: Some("the-message".to_string()),
900// ..Default::default()
901// },
902// )),
903// });
904// executor.run_until_parked();
905
906// project_a.read_with(cx_a, |project, _| {
907// let status = project.language_server_statuses().next().unwrap();
908// assert_eq!(status.name, "the-language-server");
909// assert_eq!(status.pending_work.len(), 1);
910// assert_eq!(
911// status.pending_work["the-token"].message.as_ref().unwrap(),
912// "the-message"
913// );
914// });
915
916// let project_id = active_call_a
917// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
918// .await
919// .unwrap();
920// executor.run_until_parked();
921// let project_b = client_b.build_remote_project(project_id, cx_b).await;
922
923// project_b.read_with(cx_b, |project, _| {
924// let status = project.language_server_statuses().next().unwrap();
925// assert_eq!(status.name, "the-language-server");
926// });
927
928// fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
929// token: lsp::NumberOrString::String("the-token".to_string()),
930// value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
931// lsp::WorkDoneProgressReport {
932// message: Some("the-message-2".to_string()),
933// ..Default::default()
934// },
935// )),
936// });
937// executor.run_until_parked();
938
939// project_a.read_with(cx_a, |project, _| {
940// let status = project.language_server_statuses().next().unwrap();
941// assert_eq!(status.name, "the-language-server");
942// assert_eq!(status.pending_work.len(), 1);
943// assert_eq!(
944// status.pending_work["the-token"].message.as_ref().unwrap(),
945// "the-message-2"
946// );
947// });
948
949// project_b.read_with(cx_b, |project, _| {
950// let status = project.language_server_statuses().next().unwrap();
951// assert_eq!(status.name, "the-language-server");
952// assert_eq!(status.pending_work.len(), 1);
953// assert_eq!(
954// status.pending_work["the-token"].message.as_ref().unwrap(),
955// "the-message-2"
956// );
957// });
958// }
959
960// #[gpui::test(iterations = 10)]
961// async fn test_share_project(
962// executor: BackgroundExecutor,
963// cx_a: &mut TestAppContext,
964// cx_b: &mut TestAppContext,
965// cx_c: &mut TestAppContext,
966// ) {
967// let window_b = cx_b.add_empty_window();
968// let mut server = TestServer::start(executor).await;
969// let client_a = server.create_client(cx_a, "user_a").await;
970// let client_b = server.create_client(cx_b, "user_b").await;
971// let client_c = server.create_client(cx_c, "user_c").await;
972// server
973// .make_contacts(&mut [(&client_a, cx_a), (&client_b, cx_b), (&client_c, cx_c)])
974// .await;
975// let active_call_a = cx_a.read(ActiveCall::global);
976// let active_call_b = cx_b.read(ActiveCall::global);
977// let active_call_c = cx_c.read(ActiveCall::global);
978
979// client_a
980// .fs()
981// .insert_tree(
982// "/a",
983// json!({
984// ".gitignore": "ignored-dir",
985// "a.txt": "a-contents",
986// "b.txt": "b-contents",
987// "ignored-dir": {
988// "c.txt": "",
989// "d.txt": "",
990// }
991// }),
992// )
993// .await;
994
995// // Invite client B to collaborate on a project
996// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
997// active_call_a
998// .update(cx_a, |call, cx| {
999// call.invite(client_b.user_id().unwrap(), Some(project_a.clone()), cx)
1000// })
1001// .await
1002// .unwrap();
1003
1004// // Join that project as client B
1005
1006// let incoming_call_b = active_call_b.read_with(cx_b, |call, _| call.incoming());
1007// executor.run_until_parked();
1008// let call = incoming_call_b.borrow().clone().unwrap();
1009// assert_eq!(call.calling_user.github_login, "user_a");
1010// let initial_project = call.initial_project.unwrap();
1011// active_call_b
1012// .update(cx_b, |call, cx| call.accept_incoming(cx))
1013// .await
1014// .unwrap();
1015// let client_b_peer_id = client_b.peer_id().unwrap();
1016// let project_b = client_b
1017// .build_remote_project(initial_project.id, cx_b)
1018// .await;
1019
1020// let replica_id_b = project_b.read_with(cx_b, |project, _| project.replica_id());
1021
1022// executor.run_until_parked();
1023
1024// project_a.read_with(cx_a, |project, _| {
1025// let client_b_collaborator = project.collaborators().get(&client_b_peer_id).unwrap();
1026// assert_eq!(client_b_collaborator.replica_id, replica_id_b);
1027// });
1028
1029// project_b.read_with(cx_b, |project, cx| {
1030// let worktree = project.worktrees().next().unwrap().read(cx);
1031// assert_eq!(
1032// worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
1033// [
1034// Path::new(".gitignore"),
1035// Path::new("a.txt"),
1036// Path::new("b.txt"),
1037// Path::new("ignored-dir"),
1038// ]
1039// );
1040// });
1041
1042// project_b
1043// .update(cx_b, |project, cx| {
1044// let worktree = project.worktrees().next().unwrap();
1045// let entry = worktree.read(cx).entry_for_path("ignored-dir").unwrap();
1046// project.expand_entry(worktree_id, entry.id, cx).unwrap()
1047// })
1048// .await
1049// .unwrap();
1050
1051// project_b.read_with(cx_b, |project, cx| {
1052// let worktree = project.worktrees().next().unwrap().read(cx);
1053// assert_eq!(
1054// worktree.paths().map(AsRef::as_ref).collect::<Vec<_>>(),
1055// [
1056// Path::new(".gitignore"),
1057// Path::new("a.txt"),
1058// Path::new("b.txt"),
1059// Path::new("ignored-dir"),
1060// Path::new("ignored-dir/c.txt"),
1061// Path::new("ignored-dir/d.txt"),
1062// ]
1063// );
1064// });
1065
1066// // Open the same file as client B and client A.
1067// let buffer_b = project_b
1068// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
1069// .await
1070// .unwrap();
1071
1072// buffer_b.read_with(cx_b, |buf, _| assert_eq!(buf.text(), "b-contents"));
1073
1074// project_a.read_with(cx_a, |project, cx| {
1075// assert!(project.has_open_buffer((worktree_id, "b.txt"), cx))
1076// });
1077// let buffer_a = project_a
1078// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "b.txt"), cx))
1079// .await
1080// .unwrap();
1081
1082// let editor_b = window_b.build_view(cx_b, |cx| Editor::for_buffer(buffer_b, None, cx));
1083
1084// // Client A sees client B's selection
1085// executor.run_until_parked();
1086
1087// buffer_a.read_with(cx_a, |buffer, _| {
1088// buffer
1089// .snapshot()
1090// .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
1091// .count()
1092// == 1
1093// });
1094
1095// // Edit the buffer as client B and see that edit as client A.
1096// editor_b.update(cx_b, |editor, cx| editor.handle_input("ok, ", cx));
1097// executor.run_until_parked();
1098
1099// buffer_a.read_with(cx_a, |buffer, _| {
1100// assert_eq!(buffer.text(), "ok, b-contents")
1101// });
1102
1103// // Client B can invite client C on a project shared by client A.
1104// active_call_b
1105// .update(cx_b, |call, cx| {
1106// call.invite(client_c.user_id().unwrap(), Some(project_b.clone()), cx)
1107// })
1108// .await
1109// .unwrap();
1110
1111// let incoming_call_c = active_call_c.read_with(cx_c, |call, _| call.incoming());
1112// executor.run_until_parked();
1113// let call = incoming_call_c.borrow().clone().unwrap();
1114// assert_eq!(call.calling_user.github_login, "user_b");
1115// let initial_project = call.initial_project.unwrap();
1116// active_call_c
1117// .update(cx_c, |call, cx| call.accept_incoming(cx))
1118// .await
1119// .unwrap();
1120// let _project_c = client_c
1121// .build_remote_project(initial_project.id, cx_c)
1122// .await;
1123
1124// // Client B closes the editor, and client A sees client B's selections removed.
1125// cx_b.update(move |_| drop(editor_b));
1126// executor.run_until_parked();
1127
1128// buffer_a.read_with(cx_a, |buffer, _| {
1129// buffer
1130// .snapshot()
1131// .remote_selections_in_range(Anchor::MIN..Anchor::MAX)
1132// .count()
1133// == 0
1134// });
1135// }
1136
1137// #[gpui::test(iterations = 10)]
1138// async fn test_on_input_format_from_host_to_guest(
1139// executor: BackgroundExecutor,
1140// cx_a: &mut TestAppContext,
1141// cx_b: &mut TestAppContext,
1142// ) {
1143// let mut server = TestServer::start(&executor).await;
1144// let client_a = server.create_client(cx_a, "user_a").await;
1145// let client_b = server.create_client(cx_b, "user_b").await;
1146// server
1147// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1148// .await;
1149// let active_call_a = cx_a.read(ActiveCall::global);
1150
1151// // Set up a fake language server.
1152// let mut language = Language::new(
1153// LanguageConfig {
1154// name: "Rust".into(),
1155// path_suffixes: vec!["rs".to_string()],
1156// ..Default::default()
1157// },
1158// Some(tree_sitter_rust::language()),
1159// );
1160// let mut fake_language_servers = language
1161// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1162// capabilities: lsp::ServerCapabilities {
1163// document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1164// first_trigger_character: ":".to_string(),
1165// more_trigger_character: Some(vec![">".to_string()]),
1166// }),
1167// ..Default::default()
1168// },
1169// ..Default::default()
1170// }))
1171// .await;
1172// client_a.language_registry().add(Arc::new(language));
1173
1174// client_a
1175// .fs()
1176// .insert_tree(
1177// "/a",
1178// json!({
1179// "main.rs": "fn main() { a }",
1180// "other.rs": "// Test file",
1181// }),
1182// )
1183// .await;
1184// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1185// let project_id = active_call_a
1186// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1187// .await
1188// .unwrap();
1189// let project_b = client_b.build_remote_project(project_id, cx_b).await;
1190
1191// // Open a file in an editor as the host.
1192// let buffer_a = project_a
1193// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1194// .await
1195// .unwrap();
1196// let window_a = cx_a.add_empty_window();
1197// let editor_a = window_a
1198// .update(cx_a, |_, cx| {
1199// cx.build_view(|cx| Editor::for_buffer(buffer_a, Some(project_a.clone()), cx))
1200// })
1201// .unwrap();
1202
1203// let fake_language_server = fake_language_servers.next().await.unwrap();
1204// executor.run_until_parked();
1205
1206// // Receive an OnTypeFormatting request as the host's language server.
1207// // Return some formattings from the host's language server.
1208// fake_language_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(
1209// |params, _| async move {
1210// assert_eq!(
1211// params.text_document_position.text_document.uri,
1212// lsp::Url::from_file_path("/a/main.rs").unwrap(),
1213// );
1214// assert_eq!(
1215// params.text_document_position.position,
1216// lsp::Position::new(0, 14),
1217// );
1218
1219// Ok(Some(vec![lsp::TextEdit {
1220// new_text: "~<".to_string(),
1221// range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1222// }]))
1223// },
1224// );
1225
1226// // Open the buffer on the guest and see that the formattings worked
1227// let buffer_b = project_b
1228// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1229// .await
1230// .unwrap();
1231
1232// // Type a on type formatting trigger character as the guest.
1233// editor_a.update(cx_a, |editor, cx| {
1234// cx.focus(&editor_a);
1235// editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1236// editor.handle_input(">", cx);
1237// });
1238
1239// executor.run_until_parked();
1240
1241// buffer_b.read_with(cx_b, |buffer, _| {
1242// assert_eq!(buffer.text(), "fn main() { a>~< }")
1243// });
1244
1245// // Undo should remove LSP edits first
1246// editor_a.update(cx_a, |editor, cx| {
1247// assert_eq!(editor.text(cx), "fn main() { a>~< }");
1248// editor.undo(&Undo, cx);
1249// assert_eq!(editor.text(cx), "fn main() { a> }");
1250// });
1251// executor.run_until_parked();
1252
1253// buffer_b.read_with(cx_b, |buffer, _| {
1254// assert_eq!(buffer.text(), "fn main() { a> }")
1255// });
1256
1257// editor_a.update(cx_a, |editor, cx| {
1258// assert_eq!(editor.text(cx), "fn main() { a> }");
1259// editor.undo(&Undo, cx);
1260// assert_eq!(editor.text(cx), "fn main() { a }");
1261// });
1262// executor.run_until_parked();
1263
1264// buffer_b.read_with(cx_b, |buffer, _| {
1265// assert_eq!(buffer.text(), "fn main() { a }")
1266// });
1267// }
1268
1269// #[gpui::test(iterations = 10)]
1270// async fn test_on_input_format_from_guest_to_host(
1271// executor: BackgroundExecutor,
1272// cx_a: &mut TestAppContext,
1273// cx_b: &mut TestAppContext,
1274// ) {
1275// let mut server = TestServer::start(&executor).await;
1276// let client_a = server.create_client(cx_a, "user_a").await;
1277// let client_b = server.create_client(cx_b, "user_b").await;
1278// server
1279// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1280// .await;
1281// let active_call_a = cx_a.read(ActiveCall::global);
1282
1283// // Set up a fake language server.
1284// let mut language = Language::new(
1285// LanguageConfig {
1286// name: "Rust".into(),
1287// path_suffixes: vec!["rs".to_string()],
1288// ..Default::default()
1289// },
1290// Some(tree_sitter_rust::language()),
1291// );
1292// let mut fake_language_servers = language
1293// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1294// capabilities: lsp::ServerCapabilities {
1295// document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
1296// first_trigger_character: ":".to_string(),
1297// more_trigger_character: Some(vec![">".to_string()]),
1298// }),
1299// ..Default::default()
1300// },
1301// ..Default::default()
1302// }))
1303// .await;
1304// client_a.language_registry().add(Arc::new(language));
1305
1306// client_a
1307// .fs()
1308// .insert_tree(
1309// "/a",
1310// json!({
1311// "main.rs": "fn main() { a }",
1312// "other.rs": "// Test file",
1313// }),
1314// )
1315// .await;
1316// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1317// let project_id = active_call_a
1318// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1319// .await
1320// .unwrap();
1321// let project_b = client_b.build_remote_project(project_id, cx_b).await;
1322
1323// // Open a file in an editor as the guest.
1324// let buffer_b = project_b
1325// .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1326// .await
1327// .unwrap();
1328// let window_b = cx_b.add_empty_window();
1329// let editor_b = window_b.build_view(cx_b, |cx| {
1330// Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
1331// });
1332
1333// let fake_language_server = fake_language_servers.next().await.unwrap();
1334// executor.run_until_parked();
1335// // Type a on type formatting trigger character as the guest.
1336// editor_b.update(cx_b, |editor, cx| {
1337// editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1338// editor.handle_input(":", cx);
1339// cx.focus(&editor_b);
1340// });
1341
1342// // Receive an OnTypeFormatting request as the host's language server.
1343// // Return some formattings from the host's language server.
1344// cx_a.foreground().start_waiting();
1345// fake_language_server
1346// .handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
1347// assert_eq!(
1348// params.text_document_position.text_document.uri,
1349// lsp::Url::from_file_path("/a/main.rs").unwrap(),
1350// );
1351// assert_eq!(
1352// params.text_document_position.position,
1353// lsp::Position::new(0, 14),
1354// );
1355
1356// Ok(Some(vec![lsp::TextEdit {
1357// new_text: "~:".to_string(),
1358// range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
1359// }]))
1360// })
1361// .next()
1362// .await
1363// .unwrap();
1364// cx_a.foreground().finish_waiting();
1365
1366// // Open the buffer on the host and see that the formattings worked
1367// let buffer_a = project_a
1368// .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
1369// .await
1370// .unwrap();
1371// executor.run_until_parked();
1372
1373// buffer_a.read_with(cx_a, |buffer, _| {
1374// assert_eq!(buffer.text(), "fn main() { a:~: }")
1375// });
1376
1377// // Undo should remove LSP edits first
1378// editor_b.update(cx_b, |editor, cx| {
1379// assert_eq!(editor.text(cx), "fn main() { a:~: }");
1380// editor.undo(&Undo, cx);
1381// assert_eq!(editor.text(cx), "fn main() { a: }");
1382// });
1383// executor.run_until_parked();
1384
1385// buffer_a.read_with(cx_a, |buffer, _| {
1386// assert_eq!(buffer.text(), "fn main() { a: }")
1387// });
1388
1389// editor_b.update(cx_b, |editor, cx| {
1390// assert_eq!(editor.text(cx), "fn main() { a: }");
1391// editor.undo(&Undo, cx);
1392// assert_eq!(editor.text(cx), "fn main() { a }");
1393// });
1394// executor.run_until_parked();
1395
1396// buffer_a.read_with(cx_a, |buffer, _| {
1397// assert_eq!(buffer.text(), "fn main() { a }")
1398// });
1399// }
1400
1401// #[gpui::test(iterations = 10)]
1402// async fn test_mutual_editor_inlay_hint_cache_update(
1403// executor: BackgroundExecutor,
1404// cx_a: &mut TestAppContext,
1405// cx_b: &mut TestAppContext,
1406// ) {
1407// let mut server = TestServer::start(&executor).await;
1408// let client_a = server.create_client(cx_a, "user_a").await;
1409// let client_b = server.create_client(cx_b, "user_b").await;
1410// server
1411// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1412// .await;
1413// let active_call_a = cx_a.read(ActiveCall::global);
1414// let active_call_b = cx_b.read(ActiveCall::global);
1415
1416// cx_a.update(editor::init);
1417// cx_b.update(editor::init);
1418
1419// cx_a.update(|cx| {
1420// cx.update_global(|store: &mut SettingsStore, cx| {
1421// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1422// settings.defaults.inlay_hints = Some(InlayHintSettings {
1423// enabled: true,
1424// show_type_hints: true,
1425// show_parameter_hints: false,
1426// show_other_hints: true,
1427// })
1428// });
1429// });
1430// });
1431// cx_b.update(|cx| {
1432// cx.update_global(|store: &mut SettingsStore, cx| {
1433// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1434// settings.defaults.inlay_hints = Some(InlayHintSettings {
1435// enabled: true,
1436// show_type_hints: true,
1437// show_parameter_hints: false,
1438// show_other_hints: true,
1439// })
1440// });
1441// });
1442// });
1443
1444// let mut language = Language::new(
1445// LanguageConfig {
1446// name: "Rust".into(),
1447// path_suffixes: vec!["rs".to_string()],
1448// ..Default::default()
1449// },
1450// Some(tree_sitter_rust::language()),
1451// );
1452// let mut fake_language_servers = language
1453// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1454// capabilities: lsp::ServerCapabilities {
1455// inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1456// ..Default::default()
1457// },
1458// ..Default::default()
1459// }))
1460// .await;
1461// let language = Arc::new(language);
1462// client_a.language_registry().add(Arc::clone(&language));
1463// client_b.language_registry().add(language);
1464
1465// // Client A opens a project.
1466// client_a
1467// .fs()
1468// .insert_tree(
1469// "/a",
1470// json!({
1471// "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
1472// "other.rs": "// Test file",
1473// }),
1474// )
1475// .await;
1476// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1477// active_call_a
1478// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1479// .await
1480// .unwrap();
1481// let project_id = active_call_a
1482// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1483// .await
1484// .unwrap();
1485
1486// // Client B joins the project
1487// let project_b = client_b.build_remote_project(project_id, cx_b).await;
1488// active_call_b
1489// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1490// .await
1491// .unwrap();
1492
1493// let workspace_a = client_a.build_workspace(&project_a, cx_a).root_view(cx_a);
1494// cx_a.foreground().start_waiting();
1495
1496// // The host opens a rust file.
1497// let _buffer_a = project_a
1498// .update(cx_a, |project, cx| {
1499// project.open_local_buffer("/a/main.rs", cx)
1500// })
1501// .await
1502// .unwrap();
1503// let fake_language_server = fake_language_servers.next().await.unwrap();
1504// let editor_a = workspace_a
1505// .update(cx_a, |workspace, cx| {
1506// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1507// })
1508// .await
1509// .unwrap()
1510// .downcast::<Editor>()
1511// .unwrap();
1512
1513// // Set up the language server to return an additional inlay hint on each request.
1514// let edits_made = Arc::new(AtomicUsize::new(0));
1515// let closure_edits_made = Arc::clone(&edits_made);
1516// fake_language_server
1517// .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1518// let task_edits_made = Arc::clone(&closure_edits_made);
1519// async move {
1520// assert_eq!(
1521// params.text_document.uri,
1522// lsp::Url::from_file_path("/a/main.rs").unwrap(),
1523// );
1524// let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
1525// Ok(Some(vec![lsp::InlayHint {
1526// position: lsp::Position::new(0, edits_made as u32),
1527// label: lsp::InlayHintLabel::String(edits_made.to_string()),
1528// kind: None,
1529// text_edits: None,
1530// tooltip: None,
1531// padding_left: None,
1532// padding_right: None,
1533// data: None,
1534// }]))
1535// }
1536// })
1537// .next()
1538// .await
1539// .unwrap();
1540
1541// executor.run_until_parked();
1542
1543// let initial_edit = edits_made.load(atomic::Ordering::Acquire);
1544// editor_a.update(cx_a, |editor, _| {
1545// assert_eq!(
1546// vec![initial_edit.to_string()],
1547// extract_hint_labels(editor),
1548// "Host should get its first hints when opens an editor"
1549// );
1550// let inlay_cache = editor.inlay_hint_cache();
1551// assert_eq!(
1552// inlay_cache.version(),
1553// 1,
1554// "Host editor update the cache version after every cache/view change",
1555// );
1556// });
1557// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
1558// let editor_b = workspace_b
1559// .update(cx_b, |workspace, cx| {
1560// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1561// })
1562// .await
1563// .unwrap()
1564// .downcast::<Editor>()
1565// .unwrap();
1566
1567// executor.run_until_parked();
1568// editor_b.update(cx_b, |editor, _| {
1569// assert_eq!(
1570// vec![initial_edit.to_string()],
1571// extract_hint_labels(editor),
1572// "Client should get its first hints when opens an editor"
1573// );
1574// let inlay_cache = editor.inlay_hint_cache();
1575// assert_eq!(
1576// inlay_cache.version(),
1577// 1,
1578// "Guest editor update the cache version after every cache/view change"
1579// );
1580// });
1581
1582// let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1583// editor_b.update(cx_b, |editor, cx| {
1584// editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
1585// editor.handle_input(":", cx);
1586// cx.focus(&editor_b);
1587// });
1588
1589// executor.run_until_parked();
1590// editor_a.update(cx_a, |editor, _| {
1591// assert_eq!(
1592// vec![after_client_edit.to_string()],
1593// extract_hint_labels(editor),
1594// );
1595// let inlay_cache = editor.inlay_hint_cache();
1596// assert_eq!(inlay_cache.version(), 2);
1597// });
1598// editor_b.update(cx_b, |editor, _| {
1599// assert_eq!(
1600// vec![after_client_edit.to_string()],
1601// extract_hint_labels(editor),
1602// );
1603// let inlay_cache = editor.inlay_hint_cache();
1604// assert_eq!(inlay_cache.version(), 2);
1605// });
1606
1607// let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1608// editor_a.update(cx_a, |editor, cx| {
1609// editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
1610// editor.handle_input("a change to increment both buffers' versions", cx);
1611// cx.focus(&editor_a);
1612// });
1613
1614// executor.run_until_parked();
1615// editor_a.update(cx_a, |editor, _| {
1616// assert_eq!(
1617// vec![after_host_edit.to_string()],
1618// extract_hint_labels(editor),
1619// );
1620// let inlay_cache = editor.inlay_hint_cache();
1621// assert_eq!(inlay_cache.version(), 3);
1622// });
1623// editor_b.update(cx_b, |editor, _| {
1624// assert_eq!(
1625// vec![after_host_edit.to_string()],
1626// extract_hint_labels(editor),
1627// );
1628// let inlay_cache = editor.inlay_hint_cache();
1629// assert_eq!(inlay_cache.version(), 3);
1630// });
1631
1632// let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
1633// fake_language_server
1634// .request::<lsp::request::InlayHintRefreshRequest>(())
1635// .await
1636// .expect("inlay refresh request failed");
1637
1638// executor.run_until_parked();
1639// editor_a.update(cx_a, |editor, _| {
1640// assert_eq!(
1641// vec![after_special_edit_for_refresh.to_string()],
1642// extract_hint_labels(editor),
1643// "Host should react to /refresh LSP request"
1644// );
1645// let inlay_cache = editor.inlay_hint_cache();
1646// assert_eq!(
1647// inlay_cache.version(),
1648// 4,
1649// "Host should accepted all edits and bump its cache version every time"
1650// );
1651// });
1652// editor_b.update(cx_b, |editor, _| {
1653// assert_eq!(
1654// vec![after_special_edit_for_refresh.to_string()],
1655// extract_hint_labels(editor),
1656// "Guest should get a /refresh LSP request propagated by host"
1657// );
1658// let inlay_cache = editor.inlay_hint_cache();
1659// assert_eq!(
1660// inlay_cache.version(),
1661// 4,
1662// "Guest should accepted all edits and bump its cache version every time"
1663// );
1664// });
1665// }
1666
1667// #[gpui::test(iterations = 10)]
1668// async fn test_inlay_hint_refresh_is_forwarded(
1669// executor: BackgroundExecutor,
1670// cx_a: &mut TestAppContext,
1671// cx_b: &mut TestAppContext,
1672// ) {
1673// let mut server = TestServer::start(&executor).await;
1674// let client_a = server.create_client(cx_a, "user_a").await;
1675// let client_b = server.create_client(cx_b, "user_b").await;
1676// server
1677// .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
1678// .await;
1679// let active_call_a = cx_a.read(ActiveCall::global);
1680// let active_call_b = cx_b.read(ActiveCall::global);
1681
1682// cx_a.update(editor::init);
1683// cx_b.update(editor::init);
1684
1685// cx_a.update(|cx| {
1686// cx.update_global(|store: &mut SettingsStore, cx| {
1687// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1688// settings.defaults.inlay_hints = Some(InlayHintSettings {
1689// enabled: false,
1690// show_type_hints: false,
1691// show_parameter_hints: false,
1692// show_other_hints: false,
1693// })
1694// });
1695// });
1696// });
1697// cx_b.update(|cx| {
1698// cx.update_global(|store: &mut SettingsStore, cx| {
1699// store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
1700// settings.defaults.inlay_hints = Some(InlayHintSettings {
1701// enabled: true,
1702// show_type_hints: true,
1703// show_parameter_hints: true,
1704// show_other_hints: true,
1705// })
1706// });
1707// });
1708// });
1709
1710// let mut language = Language::new(
1711// LanguageConfig {
1712// name: "Rust".into(),
1713// path_suffixes: vec!["rs".to_string()],
1714// ..Default::default()
1715// },
1716// Some(tree_sitter_rust::language()),
1717// );
1718// let mut fake_language_servers = language
1719// .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
1720// capabilities: lsp::ServerCapabilities {
1721// inlay_hint_provider: Some(lsp::OneOf::Left(true)),
1722// ..Default::default()
1723// },
1724// ..Default::default()
1725// }))
1726// .await;
1727// let language = Arc::new(language);
1728// client_a.language_registry().add(Arc::clone(&language));
1729// client_b.language_registry().add(language);
1730
1731// client_a
1732// .fs()
1733// .insert_tree(
1734// "/a",
1735// json!({
1736// "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
1737// "other.rs": "// Test file",
1738// }),
1739// )
1740// .await;
1741// let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
1742// active_call_a
1743// .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
1744// .await
1745// .unwrap();
1746// let project_id = active_call_a
1747// .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
1748// .await
1749// .unwrap();
1750
1751// let project_b = client_b.build_remote_project(project_id, cx_b).await;
1752// active_call_b
1753// .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
1754// .await
1755// .unwrap();
1756
1757// let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
1758// let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
1759// cx_a.foreground().start_waiting();
1760// cx_b.foreground().start_waiting();
1761
1762// let editor_a = workspace_a
1763// .update(cx_a, |workspace, cx| {
1764// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1765// })
1766// .await
1767// .unwrap()
1768// .downcast::<Editor>()
1769// .unwrap();
1770
1771// let editor_b = workspace_b
1772// .update(cx_b, |workspace, cx| {
1773// workspace.open_path((worktree_id, "main.rs"), None, true, cx)
1774// })
1775// .await
1776// .unwrap()
1777// .downcast::<Editor>()
1778// .unwrap();
1779
1780// let other_hints = Arc::new(AtomicBool::new(false));
1781// let fake_language_server = fake_language_servers.next().await.unwrap();
1782// let closure_other_hints = Arc::clone(&other_hints);
1783// fake_language_server
1784// .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
1785// let task_other_hints = Arc::clone(&closure_other_hints);
1786// async move {
1787// assert_eq!(
1788// params.text_document.uri,
1789// lsp::Url::from_file_path("/a/main.rs").unwrap(),
1790// );
1791// let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
1792// let character = if other_hints { 0 } else { 2 };
1793// let label = if other_hints {
1794// "other hint"
1795// } else {
1796// "initial hint"
1797// };
1798// Ok(Some(vec![lsp::InlayHint {
1799// position: lsp::Position::new(0, character),
1800// label: lsp::InlayHintLabel::String(label.to_string()),
1801// kind: None,
1802// text_edits: None,
1803// tooltip: None,
1804// padding_left: None,
1805// padding_right: None,
1806// data: None,
1807// }]))
1808// }
1809// })
1810// .next()
1811// .await
1812// .unwrap();
1813// cx_a.foreground().finish_waiting();
1814// cx_b.foreground().finish_waiting();
1815
1816// executor.run_until_parked();
1817// editor_a.update(cx_a, |editor, _| {
1818// assert!(
1819// extract_hint_labels(editor).is_empty(),
1820// "Host should get no hints due to them turned off"
1821// );
1822// let inlay_cache = editor.inlay_hint_cache();
1823// assert_eq!(
1824// inlay_cache.version(),
1825// 0,
1826// "Turned off hints should not generate version updates"
1827// );
1828// });
1829
1830// executor.run_until_parked();
1831// editor_b.update(cx_b, |editor, _| {
1832// assert_eq!(
1833// vec!["initial hint".to_string()],
1834// extract_hint_labels(editor),
1835// "Client should get its first hints when opens an editor"
1836// );
1837// let inlay_cache = editor.inlay_hint_cache();
1838// assert_eq!(
1839// inlay_cache.version(),
1840// 1,
1841// "Should update cache verison after first hints"
1842// );
1843// });
1844
1845// other_hints.fetch_or(true, atomic::Ordering::Release);
1846// fake_language_server
1847// .request::<lsp::request::InlayHintRefreshRequest>(())
1848// .await
1849// .expect("inlay refresh request failed");
1850// executor.run_until_parked();
1851// editor_a.update(cx_a, |editor, _| {
1852// assert!(
1853// extract_hint_labels(editor).is_empty(),
1854// "Host should get nop hints due to them turned off, even after the /refresh"
1855// );
1856// let inlay_cache = editor.inlay_hint_cache();
1857// assert_eq!(
1858// inlay_cache.version(),
1859// 0,
1860// "Turned off hints should not generate version updates, again"
1861// );
1862// });
1863
1864// executor.run_until_parked();
1865// editor_b.update(cx_b, |editor, _| {
1866// assert_eq!(
1867// vec!["other hint".to_string()],
1868// extract_hint_labels(editor),
1869// "Guest should get a /refresh LSP request propagated by host despite host hints are off"
1870// );
1871// let inlay_cache = editor.inlay_hint_cache();
1872// assert_eq!(
1873// inlay_cache.version(),
1874// 2,
1875// "Guest should accepted all edits and bump its cache version every time"
1876// );
1877// });
1878// }
1879
1880// fn extract_hint_labels(editor: &Editor) -> Vec<String> {
1881// let mut labels = Vec::new();
1882// for hint in editor.inlay_hint_cache().hints() {
1883// match hint.label {
1884// project::InlayHintLabel::String(s) => labels.push(s),
1885// _ => unreachable!(),
1886// }
1887// }
1888// labels
1889// }