Uncomment tests

Mikayla created

Change summary

crates/collab2/src/tests/channel_buffer_tests.rs   |   43 
crates/collab2/src/tests/editor_tests.rs           |  758 ++++++++
crates/collab2/src/tests/integration_tests.rs      | 1505 +++++++--------
crates/editor2/src/editor_tests.rs                 |   49 
crates/editor2/src/test.rs                         |    4 
crates/editor2/src/test/editor_lsp_test_context.rs |  594 +++---
crates/zed2/src/main.rs                            |    3 
7 files changed, 1,845 insertions(+), 1,111 deletions(-)

Detailed changes

crates/collab2/src/tests/channel_buffer_tests.rs 🔗

@@ -282,28 +282,27 @@ async fn test_core_channel_buffers(
 //     });
 // }
 
-//todo!(editor)
-// #[track_caller]
-// fn assert_remote_selections(
-//     editor: &mut Editor,
-//     expected_selections: &[(Option<ParticipantIndex>, Range<usize>)],
-//     cx: &mut ViewContext<Editor>,
-// ) {
-//     let snapshot = editor.snapshot(cx);
-//     let range = Anchor::min()..Anchor::max();
-//     let remote_selections = snapshot
-//         .remote_selections_in_range(&range, editor.collaboration_hub().unwrap(), cx)
-//         .map(|s| {
-//             let start = s.selection.start.to_offset(&snapshot.buffer_snapshot);
-//             let end = s.selection.end.to_offset(&snapshot.buffer_snapshot);
-//             (s.participant_index, start..end)
-//         })
-//         .collect::<Vec<_>>();
-//     assert_eq!(
-//         remote_selections, expected_selections,
-//         "incorrect remote selections"
-//     );
-// }
+#[track_caller]
+fn assert_remote_selections(
+    editor: &mut Editor,
+    expected_selections: &[(Option<ParticipantIndex>, Range<usize>)],
+    cx: &mut ViewContext<Editor>,
+) {
+    let snapshot = editor.snapshot(cx);
+    let range = Anchor::min()..Anchor::max();
+    let remote_selections = snapshot
+        .remote_selections_in_range(&range, editor.collaboration_hub().unwrap(), cx)
+        .map(|s| {
+            let start = s.selection.start.to_offset(&snapshot.buffer_snapshot);
+            let end = s.selection.end.to_offset(&snapshot.buffer_snapshot);
+            (s.participant_index, start..end)
+        })
+        .collect::<Vec<_>>();
+    assert_eq!(
+        remote_selections, expected_selections,
+        "incorrect remote selections"
+    );
+}
 
 #[gpui::test]
 async fn test_multiple_handles_to_channel_buffer(

crates/collab2/src/tests/editor_tests.rs 🔗

@@ -122,7 +122,6 @@ async fn test_host_disconnect(
     project_a.read_with(cx_a, |project, _| assert!(!project.is_shared()));
 }
 
-todo!(editor)
 #[gpui::test]
 async fn test_newline_above_or_below_does_not_move_guest_cursor(
     executor: BackgroundExecutor,
@@ -216,7 +215,6 @@ async fn test_newline_above_or_below_does_not_move_guest_cursor(
     "});
 }
 
-todo!(editor)
 #[gpui::test(iterations = 10)]
 async fn test_collaborating_with_completion(
     executor: BackgroundExecutor,
@@ -402,7 +400,7 @@ async fn test_collaborating_with_completion(
         );
     });
 }
-todo!(editor)
+
 #[gpui::test(iterations = 10)]
 async fn test_collaborating_with_code_actions(
     executor: BackgroundExecutor,
@@ -621,7 +619,6 @@ async fn test_collaborating_with_code_actions(
     });
 }
 
-todo!(editor)
 #[gpui::test(iterations = 10)]
 async fn test_collaborating_with_renames(
     executor: BackgroundExecutor,
@@ -815,7 +812,6 @@ async fn test_collaborating_with_renames(
     })
 }
 
-todo!(editor)
 #[gpui::test(iterations = 10)]
 async fn test_language_server_statuses(
     executor: BackgroundExecutor,
@@ -1108,3 +1104,755 @@ async fn test_share_project(
             == 0
     });
 }
+
+#[gpui::test(iterations = 10)]
+async fn test_on_input_format_from_host_to_guest(
+    executor: BackgroundExecutor,
+    cx_a: &mut TestAppContext,
+    cx_b: &mut TestAppContext,
+) {
+    let mut server = TestServer::start(&executor).await;
+    let client_a = server.create_client(cx_a, "user_a").await;
+    let client_b = server.create_client(cx_b, "user_b").await;
+    server
+        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+        .await;
+    let active_call_a = cx_a.read(ActiveCall::global);
+
+    // Set up a fake language server.
+    let mut language = Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            path_suffixes: vec!["rs".to_string()],
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    );
+    let mut fake_language_servers = language
+        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+            capabilities: lsp::ServerCapabilities {
+                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
+                    first_trigger_character: ":".to_string(),
+                    more_trigger_character: Some(vec![">".to_string()]),
+                }),
+                ..Default::default()
+            },
+            ..Default::default()
+        }))
+        .await;
+    client_a.language_registry().add(Arc::new(language));
+
+    client_a
+        .fs()
+        .insert_tree(
+            "/a",
+            json!({
+                "main.rs": "fn main() { a }",
+                "other.rs": "// Test file",
+            }),
+        )
+        .await;
+    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+    let project_id = active_call_a
+        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+        .await
+        .unwrap();
+    let project_b = client_b.build_remote_project(project_id, cx_b).await;
+
+    // Open a file in an editor as the host.
+    let buffer_a = project_a
+        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+        .await
+        .unwrap();
+    let window_a = cx_a.add_window(|_| EmptyView);
+    let editor_a = window_a.add_view(cx_a, |cx| {
+        Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)
+    });
+
+    let fake_language_server = fake_language_servers.next().await.unwrap();
+    executor.run_until_parked();
+
+    // Receive an OnTypeFormatting request as the host's language server.
+    // Return some formattings from the host's language server.
+    fake_language_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(
+        |params, _| async move {
+            assert_eq!(
+                params.text_document_position.text_document.uri,
+                lsp::Url::from_file_path("/a/main.rs").unwrap(),
+            );
+            assert_eq!(
+                params.text_document_position.position,
+                lsp::Position::new(0, 14),
+            );
+
+            Ok(Some(vec![lsp::TextEdit {
+                new_text: "~<".to_string(),
+                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
+            }]))
+        },
+    );
+
+    // Open the buffer on the guest and see that the formattings worked
+    let buffer_b = project_b
+        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+        .await
+        .unwrap();
+
+    // Type a on type formatting trigger character as the guest.
+    editor_a.update(cx_a, |editor, cx| {
+        cx.focus(&editor_a);
+        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
+        editor.handle_input(">", cx);
+    });
+
+    executor.run_until_parked();
+
+    buffer_b.read_with(cx_b, |buffer, _| {
+        assert_eq!(buffer.text(), "fn main() { a>~< }")
+    });
+
+    // Undo should remove LSP edits first
+    editor_a.update(cx_a, |editor, cx| {
+        assert_eq!(editor.text(cx), "fn main() { a>~< }");
+        editor.undo(&Undo, cx);
+        assert_eq!(editor.text(cx), "fn main() { a> }");
+    });
+    executor.run_until_parked();
+
+    buffer_b.read_with(cx_b, |buffer, _| {
+        assert_eq!(buffer.text(), "fn main() { a> }")
+    });
+
+    editor_a.update(cx_a, |editor, cx| {
+        assert_eq!(editor.text(cx), "fn main() { a> }");
+        editor.undo(&Undo, cx);
+        assert_eq!(editor.text(cx), "fn main() { a }");
+    });
+    executor.run_until_parked();
+
+    buffer_b.read_with(cx_b, |buffer, _| {
+        assert_eq!(buffer.text(), "fn main() { a }")
+    });
+}
+
+#[gpui::test(iterations = 10)]
+async fn test_on_input_format_from_guest_to_host(
+    executor: BackgroundExecutor,
+    cx_a: &mut TestAppContext,
+    cx_b: &mut TestAppContext,
+) {
+    let mut server = TestServer::start(&executor).await;
+    let client_a = server.create_client(cx_a, "user_a").await;
+    let client_b = server.create_client(cx_b, "user_b").await;
+    server
+        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+        .await;
+    let active_call_a = cx_a.read(ActiveCall::global);
+
+    // Set up a fake language server.
+    let mut language = Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            path_suffixes: vec!["rs".to_string()],
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    );
+    let mut fake_language_servers = language
+        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+            capabilities: lsp::ServerCapabilities {
+                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
+                    first_trigger_character: ":".to_string(),
+                    more_trigger_character: Some(vec![">".to_string()]),
+                }),
+                ..Default::default()
+            },
+            ..Default::default()
+        }))
+        .await;
+    client_a.language_registry().add(Arc::new(language));
+
+    client_a
+        .fs()
+        .insert_tree(
+            "/a",
+            json!({
+                "main.rs": "fn main() { a }",
+                "other.rs": "// Test file",
+            }),
+        )
+        .await;
+    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+    let project_id = active_call_a
+        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+        .await
+        .unwrap();
+    let project_b = client_b.build_remote_project(project_id, cx_b).await;
+
+    // Open a file in an editor as the guest.
+    let buffer_b = project_b
+        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+        .await
+        .unwrap();
+    let window_b = cx_b.add_window(|_| EmptyView);
+    let editor_b = window_b.add_view(cx_b, |cx| {
+        Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
+    });
+
+    let fake_language_server = fake_language_servers.next().await.unwrap();
+    executor.run_until_parked();
+    // Type a on type formatting trigger character as the guest.
+    editor_b.update(cx_b, |editor, cx| {
+        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
+        editor.handle_input(":", cx);
+        cx.focus(&editor_b);
+    });
+
+    // Receive an OnTypeFormatting request as the host's language server.
+    // Return some formattings from the host's language server.
+    cx_a.foreground().start_waiting();
+    fake_language_server
+        .handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
+            assert_eq!(
+                params.text_document_position.text_document.uri,
+                lsp::Url::from_file_path("/a/main.rs").unwrap(),
+            );
+            assert_eq!(
+                params.text_document_position.position,
+                lsp::Position::new(0, 14),
+            );
+
+            Ok(Some(vec![lsp::TextEdit {
+                new_text: "~:".to_string(),
+                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
+            }]))
+        })
+        .next()
+        .await
+        .unwrap();
+    cx_a.foreground().finish_waiting();
+
+    // Open the buffer on the host and see that the formattings worked
+    let buffer_a = project_a
+        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+        .await
+        .unwrap();
+    executor.run_until_parked();
+
+    buffer_a.read_with(cx_a, |buffer, _| {
+        assert_eq!(buffer.text(), "fn main() { a:~: }")
+    });
+
+    // Undo should remove LSP edits first
+    editor_b.update(cx_b, |editor, cx| {
+        assert_eq!(editor.text(cx), "fn main() { a:~: }");
+        editor.undo(&Undo, cx);
+        assert_eq!(editor.text(cx), "fn main() { a: }");
+    });
+    executor.run_until_parked();
+
+    buffer_a.read_with(cx_a, |buffer, _| {
+        assert_eq!(buffer.text(), "fn main() { a: }")
+    });
+
+    editor_b.update(cx_b, |editor, cx| {
+        assert_eq!(editor.text(cx), "fn main() { a: }");
+        editor.undo(&Undo, cx);
+        assert_eq!(editor.text(cx), "fn main() { a }");
+    });
+    executor.run_until_parked();
+
+    buffer_a.read_with(cx_a, |buffer, _| {
+        assert_eq!(buffer.text(), "fn main() { a }")
+    });
+}
+
+#[gpui::test(iterations = 10)]
+async fn test_mutual_editor_inlay_hint_cache_update(
+    executor: BackgroundExecutor,
+    cx_a: &mut TestAppContext,
+    cx_b: &mut TestAppContext,
+) {
+    let mut server = TestServer::start(&executor).await;
+    let client_a = server.create_client(cx_a, "user_a").await;
+    let client_b = server.create_client(cx_b, "user_b").await;
+    server
+        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+        .await;
+    let active_call_a = cx_a.read(ActiveCall::global);
+    let active_call_b = cx_b.read(ActiveCall::global);
+
+    cx_a.update(editor::init);
+    cx_b.update(editor::init);
+
+    cx_a.update(|cx| {
+        cx.update_global(|store: &mut SettingsStore, cx| {
+            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+                settings.defaults.inlay_hints = Some(InlayHintSettings {
+                    enabled: true,
+                    show_type_hints: true,
+                    show_parameter_hints: false,
+                    show_other_hints: true,
+                })
+            });
+        });
+    });
+    cx_b.update(|cx| {
+        cx.update_global(|store: &mut SettingsStore, cx| {
+            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+                settings.defaults.inlay_hints = Some(InlayHintSettings {
+                    enabled: true,
+                    show_type_hints: true,
+                    show_parameter_hints: false,
+                    show_other_hints: true,
+                })
+            });
+        });
+    });
+
+    let mut language = Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            path_suffixes: vec!["rs".to_string()],
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    );
+    let mut fake_language_servers = language
+        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+            capabilities: lsp::ServerCapabilities {
+                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
+                ..Default::default()
+            },
+            ..Default::default()
+        }))
+        .await;
+    let language = Arc::new(language);
+    client_a.language_registry().add(Arc::clone(&language));
+    client_b.language_registry().add(language);
+
+    // Client A opens a project.
+    client_a
+        .fs()
+        .insert_tree(
+            "/a",
+            json!({
+                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
+                "other.rs": "// Test file",
+            }),
+        )
+        .await;
+    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+    active_call_a
+        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+        .await
+        .unwrap();
+    let project_id = active_call_a
+        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+        .await
+        .unwrap();
+
+    // Client B joins the project
+    let project_b = client_b.build_remote_project(project_id, cx_b).await;
+    active_call_b
+        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+        .await
+        .unwrap();
+
+    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
+    cx_a.foreground().start_waiting();
+
+    // The host opens a rust file.
+    let _buffer_a = project_a
+        .update(cx_a, |project, cx| {
+            project.open_local_buffer("/a/main.rs", cx)
+        })
+        .await
+        .unwrap();
+    let fake_language_server = fake_language_servers.next().await.unwrap();
+    let editor_a = workspace_a
+        .update(cx_a, |workspace, cx| {
+            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+        })
+        .await
+        .unwrap()
+        .downcast::<Editor>()
+        .unwrap();
+
+    // Set up the language server to return an additional inlay hint on each request.
+    let edits_made = Arc::new(AtomicUsize::new(0));
+    let closure_edits_made = Arc::clone(&edits_made);
+    fake_language_server
+        .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
+            let task_edits_made = Arc::clone(&closure_edits_made);
+            async move {
+                assert_eq!(
+                    params.text_document.uri,
+                    lsp::Url::from_file_path("/a/main.rs").unwrap(),
+                );
+                let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
+                Ok(Some(vec![lsp::InlayHint {
+                    position: lsp::Position::new(0, edits_made as u32),
+                    label: lsp::InlayHintLabel::String(edits_made.to_string()),
+                    kind: None,
+                    text_edits: None,
+                    tooltip: None,
+                    padding_left: None,
+                    padding_right: None,
+                    data: None,
+                }]))
+            }
+        })
+        .next()
+        .await
+        .unwrap();
+
+    executor.run_until_parked();
+
+    let initial_edit = edits_made.load(atomic::Ordering::Acquire);
+    editor_a.update(cx_a, |editor, _| {
+        assert_eq!(
+            vec![initial_edit.to_string()],
+            extract_hint_labels(editor),
+            "Host should get its first hints when opens an editor"
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(
+            inlay_cache.version(),
+            1,
+            "Host editor update the cache version after every cache/view change",
+        );
+    });
+    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
+    let editor_b = workspace_b
+        .update(cx_b, |workspace, cx| {
+            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+        })
+        .await
+        .unwrap()
+        .downcast::<Editor>()
+        .unwrap();
+
+    executor.run_until_parked();
+    editor_b.update(cx_b, |editor, _| {
+        assert_eq!(
+            vec![initial_edit.to_string()],
+            extract_hint_labels(editor),
+            "Client should get its first hints when opens an editor"
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(
+            inlay_cache.version(),
+            1,
+            "Guest editor update the cache version after every cache/view change"
+        );
+    });
+
+    let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
+    editor_b.update(cx_b, |editor, cx| {
+        editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
+        editor.handle_input(":", cx);
+        cx.focus(&editor_b);
+    });
+
+    executor.run_until_parked();
+    editor_a.update(cx_a, |editor, _| {
+        assert_eq!(
+            vec![after_client_edit.to_string()],
+            extract_hint_labels(editor),
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(inlay_cache.version(), 2);
+    });
+    editor_b.update(cx_b, |editor, _| {
+        assert_eq!(
+            vec![after_client_edit.to_string()],
+            extract_hint_labels(editor),
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(inlay_cache.version(), 2);
+    });
+
+    let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
+    editor_a.update(cx_a, |editor, cx| {
+        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
+        editor.handle_input("a change to increment both buffers' versions", cx);
+        cx.focus(&editor_a);
+    });
+
+    executor.run_until_parked();
+    editor_a.update(cx_a, |editor, _| {
+        assert_eq!(
+            vec![after_host_edit.to_string()],
+            extract_hint_labels(editor),
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(inlay_cache.version(), 3);
+    });
+    editor_b.update(cx_b, |editor, _| {
+        assert_eq!(
+            vec![after_host_edit.to_string()],
+            extract_hint_labels(editor),
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(inlay_cache.version(), 3);
+    });
+
+    let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
+    fake_language_server
+        .request::<lsp::request::InlayHintRefreshRequest>(())
+        .await
+        .expect("inlay refresh request failed");
+
+    executor.run_until_parked();
+    editor_a.update(cx_a, |editor, _| {
+        assert_eq!(
+            vec![after_special_edit_for_refresh.to_string()],
+            extract_hint_labels(editor),
+            "Host should react to /refresh LSP request"
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(
+            inlay_cache.version(),
+            4,
+            "Host should accepted all edits and bump its cache version every time"
+        );
+    });
+    editor_b.update(cx_b, |editor, _| {
+        assert_eq!(
+            vec![after_special_edit_for_refresh.to_string()],
+            extract_hint_labels(editor),
+            "Guest should get a /refresh LSP request propagated by host"
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(
+            inlay_cache.version(),
+            4,
+            "Guest should accepted all edits and bump its cache version every time"
+        );
+    });
+}
+
+#[gpui::test(iterations = 10)]
+async fn test_inlay_hint_refresh_is_forwarded(
+    executor: BackgroundExecutor,
+    cx_a: &mut TestAppContext,
+    cx_b: &mut TestAppContext,
+) {
+    let mut server = TestServer::start(&executor).await;
+    let client_a = server.create_client(cx_a, "user_a").await;
+    let client_b = server.create_client(cx_b, "user_b").await;
+    server
+        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+        .await;
+    let active_call_a = cx_a.read(ActiveCall::global);
+    let active_call_b = cx_b.read(ActiveCall::global);
+
+    cx_a.update(editor::init);
+    cx_b.update(editor::init);
+
+    cx_a.update(|cx| {
+        cx.update_global(|store: &mut SettingsStore, cx| {
+            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+                settings.defaults.inlay_hints = Some(InlayHintSettings {
+                    enabled: false,
+                    show_type_hints: false,
+                    show_parameter_hints: false,
+                    show_other_hints: false,
+                })
+            });
+        });
+    });
+    cx_b.update(|cx| {
+        cx.update_global(|store: &mut SettingsStore, cx| {
+            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+                settings.defaults.inlay_hints = Some(InlayHintSettings {
+                    enabled: true,
+                    show_type_hints: true,
+                    show_parameter_hints: true,
+                    show_other_hints: true,
+                })
+            });
+        });
+    });
+
+    let mut language = Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            path_suffixes: vec!["rs".to_string()],
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    );
+    let mut fake_language_servers = language
+        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+            capabilities: lsp::ServerCapabilities {
+                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
+                ..Default::default()
+            },
+            ..Default::default()
+        }))
+        .await;
+    let language = Arc::new(language);
+    client_a.language_registry().add(Arc::clone(&language));
+    client_b.language_registry().add(language);
+
+    client_a
+        .fs()
+        .insert_tree(
+            "/a",
+            json!({
+                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
+                "other.rs": "// Test file",
+            }),
+        )
+        .await;
+    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+    active_call_a
+        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+        .await
+        .unwrap();
+    let project_id = active_call_a
+        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+        .await
+        .unwrap();
+
+    let project_b = client_b.build_remote_project(project_id, cx_b).await;
+    active_call_b
+        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+        .await
+        .unwrap();
+
+    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
+    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
+    cx_a.foreground().start_waiting();
+    cx_b.foreground().start_waiting();
+
+    let editor_a = workspace_a
+        .update(cx_a, |workspace, cx| {
+            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+        })
+        .await
+        .unwrap()
+        .downcast::<Editor>()
+        .unwrap();
+
+    let editor_b = workspace_b
+        .update(cx_b, |workspace, cx| {
+            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+        })
+        .await
+        .unwrap()
+        .downcast::<Editor>()
+        .unwrap();
+
+    let other_hints = Arc::new(AtomicBool::new(false));
+    let fake_language_server = fake_language_servers.next().await.unwrap();
+    let closure_other_hints = Arc::clone(&other_hints);
+    fake_language_server
+        .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
+            let task_other_hints = Arc::clone(&closure_other_hints);
+            async move {
+                assert_eq!(
+                    params.text_document.uri,
+                    lsp::Url::from_file_path("/a/main.rs").unwrap(),
+                );
+                let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
+                let character = if other_hints { 0 } else { 2 };
+                let label = if other_hints {
+                    "other hint"
+                } else {
+                    "initial hint"
+                };
+                Ok(Some(vec![lsp::InlayHint {
+                    position: lsp::Position::new(0, character),
+                    label: lsp::InlayHintLabel::String(label.to_string()),
+                    kind: None,
+                    text_edits: None,
+                    tooltip: None,
+                    padding_left: None,
+                    padding_right: None,
+                    data: None,
+                }]))
+            }
+        })
+        .next()
+        .await
+        .unwrap();
+    cx_a.foreground().finish_waiting();
+    cx_b.foreground().finish_waiting();
+
+    executor.run_until_parked();
+    editor_a.update(cx_a, |editor, _| {
+        assert!(
+            extract_hint_labels(editor).is_empty(),
+            "Host should get no hints due to them turned off"
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(
+            inlay_cache.version(),
+            0,
+            "Turned off hints should not generate version updates"
+        );
+    });
+
+    executor.run_until_parked();
+    editor_b.update(cx_b, |editor, _| {
+        assert_eq!(
+            vec!["initial hint".to_string()],
+            extract_hint_labels(editor),
+            "Client should get its first hints when opens an editor"
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(
+            inlay_cache.version(),
+            1,
+            "Should update cache verison after first hints"
+        );
+    });
+
+    other_hints.fetch_or(true, atomic::Ordering::Release);
+    fake_language_server
+        .request::<lsp::request::InlayHintRefreshRequest>(())
+        .await
+        .expect("inlay refresh request failed");
+    executor.run_until_parked();
+    editor_a.update(cx_a, |editor, _| {
+        assert!(
+            extract_hint_labels(editor).is_empty(),
+            "Host should get nop hints due to them turned off, even after the /refresh"
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(
+            inlay_cache.version(),
+            0,
+            "Turned off hints should not generate version updates, again"
+        );
+    });
+
+    executor.run_until_parked();
+    editor_b.update(cx_b, |editor, _| {
+        assert_eq!(
+            vec!["other hint".to_string()],
+            extract_hint_labels(editor),
+            "Guest should get a /refresh LSP request propagated by host despite host hints are off"
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(
+            inlay_cache.version(),
+            2,
+            "Guest should accepted all edits and bump its cache version every time"
+        );
+    });
+}
+
+fn extract_hint_labels(editor: &Editor) -> Vec<String> {
+    let mut labels = Vec::new();
+    for hint in editor.inlay_hint_cache().hints() {
+        match hint.label {
+            project::InlayHintLabel::String(s) => labels.push(s),
+            _ => unreachable!(),
+        }
+    }
+    labels
+}

crates/collab2/src/tests/integration_tests.rs 🔗

@@ -5718,757 +5718,754 @@ async fn test_join_call_after_screen_was_shared(
     });
 }
 
-//todo!(editor)
-// #[gpui::test(iterations = 10)]
-// async fn test_on_input_format_from_host_to_guest(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-// ) {
-//     let mut server = TestServer::start(&executor).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-
-//     // Set up a fake language server.
-//     let mut language = Language::new(
-//         LanguageConfig {
-//             name: "Rust".into(),
-//             path_suffixes: vec!["rs".to_string()],
-//             ..Default::default()
-//         },
-//         Some(tree_sitter_rust::language()),
-//     );
-//     let mut fake_language_servers = language
-//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-//             capabilities: lsp::ServerCapabilities {
-//                 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
-//                     first_trigger_character: ":".to_string(),
-//                     more_trigger_character: Some(vec![">".to_string()]),
-//                 }),
-//                 ..Default::default()
-//             },
-//             ..Default::default()
-//         }))
-//         .await;
-//     client_a.language_registry().add(Arc::new(language));
-
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "main.rs": "fn main() { a }",
-//                 "other.rs": "// Test file",
-//             }),
-//         )
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-
-//     // Open a file in an editor as the host.
-//     let buffer_a = project_a
-//         .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
-//         .await
-//         .unwrap();
-//     let window_a = cx_a.add_window(|_| EmptyView);
-//     let editor_a = window_a.add_view(cx_a, |cx| {
-//         Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)
-//     });
-
-//     let fake_language_server = fake_language_servers.next().await.unwrap();
-//     executor.run_until_parked();
-
-//     // Receive an OnTypeFormatting request as the host's language server.
-//     // Return some formattings from the host's language server.
-//     fake_language_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(
-//         |params, _| async move {
-//             assert_eq!(
-//                 params.text_document_position.text_document.uri,
-//                 lsp::Url::from_file_path("/a/main.rs").unwrap(),
-//             );
-//             assert_eq!(
-//                 params.text_document_position.position,
-//                 lsp::Position::new(0, 14),
-//             );
-
-//             Ok(Some(vec![lsp::TextEdit {
-//                 new_text: "~<".to_string(),
-//                 range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
-//             }]))
-//         },
-//     );
-
-//     // Open the buffer on the guest and see that the formattings worked
-//     let buffer_b = project_b
-//         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
-//         .await
-//         .unwrap();
-
-//     // Type a on type formatting trigger character as the guest.
-//     editor_a.update(cx_a, |editor, cx| {
-//         cx.focus(&editor_a);
-//         editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
-//         editor.handle_input(">", cx);
-//     });
-
-//     executor.run_until_parked();
-
-//     buffer_b.read_with(cx_b, |buffer, _| {
-//         assert_eq!(buffer.text(), "fn main() { a>~< }")
-//     });
-
-//     // Undo should remove LSP edits first
-//     editor_a.update(cx_a, |editor, cx| {
-//         assert_eq!(editor.text(cx), "fn main() { a>~< }");
-//         editor.undo(&Undo, cx);
-//         assert_eq!(editor.text(cx), "fn main() { a> }");
-//     });
-//     executor.run_until_parked();
-
-//     buffer_b.read_with(cx_b, |buffer, _| {
-//         assert_eq!(buffer.text(), "fn main() { a> }")
-//     });
-
-//     editor_a.update(cx_a, |editor, cx| {
-//         assert_eq!(editor.text(cx), "fn main() { a> }");
-//         editor.undo(&Undo, cx);
-//         assert_eq!(editor.text(cx), "fn main() { a }");
-//     });
-//     executor.run_until_parked();
-
-//     buffer_b.read_with(cx_b, |buffer, _| {
-//         assert_eq!(buffer.text(), "fn main() { a }")
-//     });
-// }
-
-// #[gpui::test(iterations = 10)]
-// async fn test_on_input_format_from_guest_to_host(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-// ) {
-//     let mut server = TestServer::start(&executor).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-
-//     // Set up a fake language server.
-//     let mut language = Language::new(
-//         LanguageConfig {
-//             name: "Rust".into(),
-//             path_suffixes: vec!["rs".to_string()],
-//             ..Default::default()
-//         },
-//         Some(tree_sitter_rust::language()),
-//     );
-//     let mut fake_language_servers = language
-//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-//             capabilities: lsp::ServerCapabilities {
-//                 document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
-//                     first_trigger_character: ":".to_string(),
-//                     more_trigger_character: Some(vec![">".to_string()]),
-//                 }),
-//                 ..Default::default()
-//             },
-//             ..Default::default()
-//         }))
-//         .await;
-//     client_a.language_registry().add(Arc::new(language));
-
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "main.rs": "fn main() { a }",
-//                 "other.rs": "// Test file",
-//             }),
-//         )
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-
-//     // Open a file in an editor as the guest.
-//     let buffer_b = project_b
-//         .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
-//         .await
-//         .unwrap();
-//     let window_b = cx_b.add_window(|_| EmptyView);
-//     let editor_b = window_b.add_view(cx_b, |cx| {
-//         Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
-//     });
-
-//     let fake_language_server = fake_language_servers.next().await.unwrap();
-//     executor.run_until_parked();
-//     // Type a on type formatting trigger character as the guest.
-//     editor_b.update(cx_b, |editor, cx| {
-//         editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
-//         editor.handle_input(":", cx);
-//         cx.focus(&editor_b);
-//     });
-
-//     // Receive an OnTypeFormatting request as the host's language server.
-//     // Return some formattings from the host's language server.
-//     cx_a.foreground().start_waiting();
-//     fake_language_server
-//         .handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
-//             assert_eq!(
-//                 params.text_document_position.text_document.uri,
-//                 lsp::Url::from_file_path("/a/main.rs").unwrap(),
-//             );
-//             assert_eq!(
-//                 params.text_document_position.position,
-//                 lsp::Position::new(0, 14),
-//             );
-
-//             Ok(Some(vec![lsp::TextEdit {
-//                 new_text: "~:".to_string(),
-//                 range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
-//             }]))
-//         })
-//         .next()
-//         .await
-//         .unwrap();
-//     cx_a.foreground().finish_waiting();
-
-//     // Open the buffer on the host and see that the formattings worked
-//     let buffer_a = project_a
-//         .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
-//         .await
-//         .unwrap();
-//     executor.run_until_parked();
-
-//     buffer_a.read_with(cx_a, |buffer, _| {
-//         assert_eq!(buffer.text(), "fn main() { a:~: }")
-//     });
-
-//     // Undo should remove LSP edits first
-//     editor_b.update(cx_b, |editor, cx| {
-//         assert_eq!(editor.text(cx), "fn main() { a:~: }");
-//         editor.undo(&Undo, cx);
-//         assert_eq!(editor.text(cx), "fn main() { a: }");
-//     });
-//     executor.run_until_parked();
-
-//     buffer_a.read_with(cx_a, |buffer, _| {
-//         assert_eq!(buffer.text(), "fn main() { a: }")
-//     });
-
-//     editor_b.update(cx_b, |editor, cx| {
-//         assert_eq!(editor.text(cx), "fn main() { a: }");
-//         editor.undo(&Undo, cx);
-//         assert_eq!(editor.text(cx), "fn main() { a }");
-//     });
-//     executor.run_until_parked();
-
-//     buffer_a.read_with(cx_a, |buffer, _| {
-//         assert_eq!(buffer.text(), "fn main() { a }")
-//     });
-// }
-
-//todo!(editor)
-// #[gpui::test(iterations = 10)]
-// async fn test_mutual_editor_inlay_hint_cache_update(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-// ) {
-//     let mut server = TestServer::start(&executor).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-//     let active_call_b = cx_b.read(ActiveCall::global);
-
-//     cx_a.update(editor::init);
-//     cx_b.update(editor::init);
-
-//     cx_a.update(|cx| {
-//         cx.update_global(|store: &mut SettingsStore, cx| {
-//             store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
-//                 settings.defaults.inlay_hints = Some(InlayHintSettings {
-//                     enabled: true,
-//                     show_type_hints: true,
-//                     show_parameter_hints: false,
-//                     show_other_hints: true,
-//                 })
-//             });
-//         });
-//     });
-//     cx_b.update(|cx| {
-//         cx.update_global(|store: &mut SettingsStore, cx| {
-//             store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
-//                 settings.defaults.inlay_hints = Some(InlayHintSettings {
-//                     enabled: true,
-//                     show_type_hints: true,
-//                     show_parameter_hints: false,
-//                     show_other_hints: true,
-//                 })
-//             });
-//         });
-//     });
-
-//     let mut language = Language::new(
-//         LanguageConfig {
-//             name: "Rust".into(),
-//             path_suffixes: vec!["rs".to_string()],
-//             ..Default::default()
-//         },
-//         Some(tree_sitter_rust::language()),
-//     );
-//     let mut fake_language_servers = language
-//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-//             capabilities: lsp::ServerCapabilities {
-//                 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
-//                 ..Default::default()
-//             },
-//             ..Default::default()
-//         }))
-//         .await;
-//     let language = Arc::new(language);
-//     client_a.language_registry().add(Arc::clone(&language));
-//     client_b.language_registry().add(language);
-
-//     // Client A opens a project.
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
-//                 "other.rs": "// Test file",
-//             }),
-//         )
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-//     active_call_a
-//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-//         .await
-//         .unwrap();
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-
-//     // Client B joins the project
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-//     active_call_b
-//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-//         .await
-//         .unwrap();
-
-//     let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
-//     cx_a.foreground().start_waiting();
-
-//     // The host opens a rust file.
-//     let _buffer_a = project_a
-//         .update(cx_a, |project, cx| {
-//             project.open_local_buffer("/a/main.rs", cx)
-//         })
-//         .await
-//         .unwrap();
-//     let fake_language_server = fake_language_servers.next().await.unwrap();
-//     let editor_a = workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id, "main.rs"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-
-//     // Set up the language server to return an additional inlay hint on each request.
-//     let edits_made = Arc::new(AtomicUsize::new(0));
-//     let closure_edits_made = Arc::clone(&edits_made);
-//     fake_language_server
-//         .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-//             let task_edits_made = Arc::clone(&closure_edits_made);
-//             async move {
-//                 assert_eq!(
-//                     params.text_document.uri,
-//                     lsp::Url::from_file_path("/a/main.rs").unwrap(),
-//                 );
-//                 let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
-//                 Ok(Some(vec![lsp::InlayHint {
-//                     position: lsp::Position::new(0, edits_made as u32),
-//                     label: lsp::InlayHintLabel::String(edits_made.to_string()),
-//                     kind: None,
-//                     text_edits: None,
-//                     tooltip: None,
-//                     padding_left: None,
-//                     padding_right: None,
-//                     data: None,
-//                 }]))
-//             }
-//         })
-//         .next()
-//         .await
-//         .unwrap();
-
-//     executor.run_until_parked();
-
-//     let initial_edit = edits_made.load(atomic::Ordering::Acquire);
-//     editor_a.update(cx_a, |editor, _| {
-//         assert_eq!(
-//             vec![initial_edit.to_string()],
-//             extract_hint_labels(editor),
-//             "Host should get its first hints when opens an editor"
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(
-//             inlay_cache.version(),
-//             1,
-//             "Host editor update the cache version after every cache/view change",
-//         );
-//     });
-//     let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
-//     let editor_b = workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id, "main.rs"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-
-//     executor.run_until_parked();
-//     editor_b.update(cx_b, |editor, _| {
-//         assert_eq!(
-//             vec![initial_edit.to_string()],
-//             extract_hint_labels(editor),
-//             "Client should get its first hints when opens an editor"
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(
-//             inlay_cache.version(),
-//             1,
-//             "Guest editor update the cache version after every cache/view change"
-//         );
-//     });
-
-//     let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
-//     editor_b.update(cx_b, |editor, cx| {
-//         editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
-//         editor.handle_input(":", cx);
-//         cx.focus(&editor_b);
-//     });
-
-//     executor.run_until_parked();
-//     editor_a.update(cx_a, |editor, _| {
-//         assert_eq!(
-//             vec![after_client_edit.to_string()],
-//             extract_hint_labels(editor),
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(inlay_cache.version(), 2);
-//     });
-//     editor_b.update(cx_b, |editor, _| {
-//         assert_eq!(
-//             vec![after_client_edit.to_string()],
-//             extract_hint_labels(editor),
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(inlay_cache.version(), 2);
-//     });
-
-//     let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
-//     editor_a.update(cx_a, |editor, cx| {
-//         editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
-//         editor.handle_input("a change to increment both buffers' versions", cx);
-//         cx.focus(&editor_a);
-//     });
-
-//     executor.run_until_parked();
-//     editor_a.update(cx_a, |editor, _| {
-//         assert_eq!(
-//             vec![after_host_edit.to_string()],
-//             extract_hint_labels(editor),
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(inlay_cache.version(), 3);
-//     });
-//     editor_b.update(cx_b, |editor, _| {
-//         assert_eq!(
-//             vec![after_host_edit.to_string()],
-//             extract_hint_labels(editor),
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(inlay_cache.version(), 3);
-//     });
-
-//     let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
-//     fake_language_server
-//         .request::<lsp::request::InlayHintRefreshRequest>(())
-//         .await
-//         .expect("inlay refresh request failed");
-
-//     executor.run_until_parked();
-//     editor_a.update(cx_a, |editor, _| {
-//         assert_eq!(
-//             vec![after_special_edit_for_refresh.to_string()],
-//             extract_hint_labels(editor),
-//             "Host should react to /refresh LSP request"
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(
-//             inlay_cache.version(),
-//             4,
-//             "Host should accepted all edits and bump its cache version every time"
-//         );
-//     });
-//     editor_b.update(cx_b, |editor, _| {
-//         assert_eq!(
-//             vec![after_special_edit_for_refresh.to_string()],
-//             extract_hint_labels(editor),
-//             "Guest should get a /refresh LSP request propagated by host"
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(
-//             inlay_cache.version(),
-//             4,
-//             "Guest should accepted all edits and bump its cache version every time"
-//         );
-//     });
-// }
-
-//todo!(editor)
-// #[gpui::test(iterations = 10)]
-// async fn test_inlay_hint_refresh_is_forwarded(
-//     executor: BackgroundExecutor,
-//     cx_a: &mut TestAppContext,
-//     cx_b: &mut TestAppContext,
-// ) {
-//     let mut server = TestServer::start(&executor).await;
-//     let client_a = server.create_client(cx_a, "user_a").await;
-//     let client_b = server.create_client(cx_b, "user_b").await;
-//     server
-//         .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
-//         .await;
-//     let active_call_a = cx_a.read(ActiveCall::global);
-//     let active_call_b = cx_b.read(ActiveCall::global);
-
-//     cx_a.update(editor::init);
-//     cx_b.update(editor::init);
-
-//     cx_a.update(|cx| {
-//         cx.update_global(|store: &mut SettingsStore, cx| {
-//             store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
-//                 settings.defaults.inlay_hints = Some(InlayHintSettings {
-//                     enabled: false,
-//                     show_type_hints: false,
-//                     show_parameter_hints: false,
-//                     show_other_hints: false,
-//                 })
-//             });
-//         });
-//     });
-//     cx_b.update(|cx| {
-//         cx.update_global(|store: &mut SettingsStore, cx| {
-//             store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
-//                 settings.defaults.inlay_hints = Some(InlayHintSettings {
-//                     enabled: true,
-//                     show_type_hints: true,
-//                     show_parameter_hints: true,
-//                     show_other_hints: true,
-//                 })
-//             });
-//         });
-//     });
-
-//     let mut language = Language::new(
-//         LanguageConfig {
-//             name: "Rust".into(),
-//             path_suffixes: vec!["rs".to_string()],
-//             ..Default::default()
-//         },
-//         Some(tree_sitter_rust::language()),
-//     );
-//     let mut fake_language_servers = language
-//         .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-//             capabilities: lsp::ServerCapabilities {
-//                 inlay_hint_provider: Some(lsp::OneOf::Left(true)),
-//                 ..Default::default()
-//             },
-//             ..Default::default()
-//         }))
-//         .await;
-//     let language = Arc::new(language);
-//     client_a.language_registry().add(Arc::clone(&language));
-//     client_b.language_registry().add(language);
-
-//     client_a
-//         .fs()
-//         .insert_tree(
-//             "/a",
-//             json!({
-//                 "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
-//                 "other.rs": "// Test file",
-//             }),
-//         )
-//         .await;
-//     let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
-//     active_call_a
-//         .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
-//         .await
-//         .unwrap();
-//     let project_id = active_call_a
-//         .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
-//         .await
-//         .unwrap();
-
-//     let project_b = client_b.build_remote_project(project_id, cx_b).await;
-//     active_call_b
-//         .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
-//         .await
-//         .unwrap();
-
-//     let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
-//     let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
-//     cx_a.foreground().start_waiting();
-//     cx_b.foreground().start_waiting();
-
-//     let editor_a = workspace_a
-//         .update(cx_a, |workspace, cx| {
-//             workspace.open_path((worktree_id, "main.rs"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-
-//     let editor_b = workspace_b
-//         .update(cx_b, |workspace, cx| {
-//             workspace.open_path((worktree_id, "main.rs"), None, true, cx)
-//         })
-//         .await
-//         .unwrap()
-//         .downcast::<Editor>()
-//         .unwrap();
-
-//     let other_hints = Arc::new(AtomicBool::new(false));
-//     let fake_language_server = fake_language_servers.next().await.unwrap();
-//     let closure_other_hints = Arc::clone(&other_hints);
-//     fake_language_server
-//         .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
-//             let task_other_hints = Arc::clone(&closure_other_hints);
-//             async move {
-//                 assert_eq!(
-//                     params.text_document.uri,
-//                     lsp::Url::from_file_path("/a/main.rs").unwrap(),
-//                 );
-//                 let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
-//                 let character = if other_hints { 0 } else { 2 };
-//                 let label = if other_hints {
-//                     "other hint"
-//                 } else {
-//                     "initial hint"
-//                 };
-//                 Ok(Some(vec![lsp::InlayHint {
-//                     position: lsp::Position::new(0, character),
-//                     label: lsp::InlayHintLabel::String(label.to_string()),
-//                     kind: None,
-//                     text_edits: None,
-//                     tooltip: None,
-//                     padding_left: None,
-//                     padding_right: None,
-//                     data: None,
-//                 }]))
-//             }
-//         })
-//         .next()
-//         .await
-//         .unwrap();
-//     cx_a.foreground().finish_waiting();
-//     cx_b.foreground().finish_waiting();
-
-//     executor.run_until_parked();
-//     editor_a.update(cx_a, |editor, _| {
-//         assert!(
-//             extract_hint_labels(editor).is_empty(),
-//             "Host should get no hints due to them turned off"
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(
-//             inlay_cache.version(),
-//             0,
-//             "Turned off hints should not generate version updates"
-//         );
-//     });
-
-//     executor.run_until_parked();
-//     editor_b.update(cx_b, |editor, _| {
-//         assert_eq!(
-//             vec!["initial hint".to_string()],
-//             extract_hint_labels(editor),
-//             "Client should get its first hints when opens an editor"
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(
-//             inlay_cache.version(),
-//             1,
-//             "Should update cache verison after first hints"
-//         );
-//     });
-
-//     other_hints.fetch_or(true, atomic::Ordering::Release);
-//     fake_language_server
-//         .request::<lsp::request::InlayHintRefreshRequest>(())
-//         .await
-//         .expect("inlay refresh request failed");
-//     executor.run_until_parked();
-//     editor_a.update(cx_a, |editor, _| {
-//         assert!(
-//             extract_hint_labels(editor).is_empty(),
-//             "Host should get nop hints due to them turned off, even after the /refresh"
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(
-//             inlay_cache.version(),
-//             0,
-//             "Turned off hints should not generate version updates, again"
-//         );
-//     });
-
-//     executor.run_until_parked();
-//     editor_b.update(cx_b, |editor, _| {
-//         assert_eq!(
-//             vec!["other hint".to_string()],
-//             extract_hint_labels(editor),
-//             "Guest should get a /refresh LSP request propagated by host despite host hints are off"
-//         );
-//         let inlay_cache = editor.inlay_hint_cache();
-//         assert_eq!(
-//             inlay_cache.version(),
-//             2,
-//             "Guest should accepted all edits and bump its cache version every time"
-//         );
-//     });
-// }
-
-// fn extract_hint_labels(editor: &Editor) -> Vec<String> {
-//     let mut labels = Vec::new();
-//     for hint in editor.inlay_hint_cache().hints() {
-//         match hint.label {
-//             project::InlayHintLabel::String(s) => labels.push(s),
-//             _ => unreachable!(),
-//         }
-//     }
-//     labels
-// }
+#[gpui::test(iterations = 10)]
+async fn test_on_input_format_from_host_to_guest(
+    executor: BackgroundExecutor,
+    cx_a: &mut TestAppContext,
+    cx_b: &mut TestAppContext,
+) {
+    let mut server = TestServer::start(&executor).await;
+    let client_a = server.create_client(cx_a, "user_a").await;
+    let client_b = server.create_client(cx_b, "user_b").await;
+    server
+        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+        .await;
+    let active_call_a = cx_a.read(ActiveCall::global);
+
+    // Set up a fake language server.
+    let mut language = Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            path_suffixes: vec!["rs".to_string()],
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    );
+    let mut fake_language_servers = language
+        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+            capabilities: lsp::ServerCapabilities {
+                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
+                    first_trigger_character: ":".to_string(),
+                    more_trigger_character: Some(vec![">".to_string()]),
+                }),
+                ..Default::default()
+            },
+            ..Default::default()
+        }))
+        .await;
+    client_a.language_registry().add(Arc::new(language));
+
+    client_a
+        .fs()
+        .insert_tree(
+            "/a",
+            json!({
+                "main.rs": "fn main() { a }",
+                "other.rs": "// Test file",
+            }),
+        )
+        .await;
+    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+    let project_id = active_call_a
+        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+        .await
+        .unwrap();
+    let project_b = client_b.build_remote_project(project_id, cx_b).await;
+
+    // Open a file in an editor as the host.
+    let buffer_a = project_a
+        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+        .await
+        .unwrap();
+    let window_a = cx_a.add_window(|_| EmptyView);
+    let editor_a = window_a.add_view(cx_a, |cx| {
+        Editor::for_buffer(buffer_a, Some(project_a.clone()), cx)
+    });
+
+    let fake_language_server = fake_language_servers.next().await.unwrap();
+    executor.run_until_parked();
+
+    // Receive an OnTypeFormatting request as the host's language server.
+    // Return some formattings from the host's language server.
+    fake_language_server.handle_request::<lsp::request::OnTypeFormatting, _, _>(
+        |params, _| async move {
+            assert_eq!(
+                params.text_document_position.text_document.uri,
+                lsp::Url::from_file_path("/a/main.rs").unwrap(),
+            );
+            assert_eq!(
+                params.text_document_position.position,
+                lsp::Position::new(0, 14),
+            );
+
+            Ok(Some(vec![lsp::TextEdit {
+                new_text: "~<".to_string(),
+                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
+            }]))
+        },
+    );
+
+    // Open the buffer on the guest and see that the formattings worked
+    let buffer_b = project_b
+        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+        .await
+        .unwrap();
+
+    // Type a on type formatting trigger character as the guest.
+    editor_a.update(cx_a, |editor, cx| {
+        cx.focus(&editor_a);
+        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
+        editor.handle_input(">", cx);
+    });
+
+    executor.run_until_parked();
+
+    buffer_b.read_with(cx_b, |buffer, _| {
+        assert_eq!(buffer.text(), "fn main() { a>~< }")
+    });
+
+    // Undo should remove LSP edits first
+    editor_a.update(cx_a, |editor, cx| {
+        assert_eq!(editor.text(cx), "fn main() { a>~< }");
+        editor.undo(&Undo, cx);
+        assert_eq!(editor.text(cx), "fn main() { a> }");
+    });
+    executor.run_until_parked();
+
+    buffer_b.read_with(cx_b, |buffer, _| {
+        assert_eq!(buffer.text(), "fn main() { a> }")
+    });
+
+    editor_a.update(cx_a, |editor, cx| {
+        assert_eq!(editor.text(cx), "fn main() { a> }");
+        editor.undo(&Undo, cx);
+        assert_eq!(editor.text(cx), "fn main() { a }");
+    });
+    executor.run_until_parked();
+
+    buffer_b.read_with(cx_b, |buffer, _| {
+        assert_eq!(buffer.text(), "fn main() { a }")
+    });
+}
+
+#[gpui::test(iterations = 10)]
+async fn test_on_input_format_from_guest_to_host(
+    executor: BackgroundExecutor,
+    cx_a: &mut TestAppContext,
+    cx_b: &mut TestAppContext,
+) {
+    let mut server = TestServer::start(&executor).await;
+    let client_a = server.create_client(cx_a, "user_a").await;
+    let client_b = server.create_client(cx_b, "user_b").await;
+    server
+        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+        .await;
+    let active_call_a = cx_a.read(ActiveCall::global);
+
+    // Set up a fake language server.
+    let mut language = Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            path_suffixes: vec!["rs".to_string()],
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    );
+    let mut fake_language_servers = language
+        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+            capabilities: lsp::ServerCapabilities {
+                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
+                    first_trigger_character: ":".to_string(),
+                    more_trigger_character: Some(vec![">".to_string()]),
+                }),
+                ..Default::default()
+            },
+            ..Default::default()
+        }))
+        .await;
+    client_a.language_registry().add(Arc::new(language));
+
+    client_a
+        .fs()
+        .insert_tree(
+            "/a",
+            json!({
+                "main.rs": "fn main() { a }",
+                "other.rs": "// Test file",
+            }),
+        )
+        .await;
+    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+    let project_id = active_call_a
+        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+        .await
+        .unwrap();
+    let project_b = client_b.build_remote_project(project_id, cx_b).await;
+
+    // Open a file in an editor as the guest.
+    let buffer_b = project_b
+        .update(cx_b, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+        .await
+        .unwrap();
+    let window_b = cx_b.add_window(|_| EmptyView);
+    let editor_b = window_b.add_view(cx_b, |cx| {
+        Editor::for_buffer(buffer_b, Some(project_b.clone()), cx)
+    });
+
+    let fake_language_server = fake_language_servers.next().await.unwrap();
+    executor.run_until_parked();
+    // Type a on type formatting trigger character as the guest.
+    editor_b.update(cx_b, |editor, cx| {
+        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
+        editor.handle_input(":", cx);
+        cx.focus(&editor_b);
+    });
+
+    // Receive an OnTypeFormatting request as the host's language server.
+    // Return some formattings from the host's language server.
+    cx_a.foreground().start_waiting();
+    fake_language_server
+        .handle_request::<lsp::request::OnTypeFormatting, _, _>(|params, _| async move {
+            assert_eq!(
+                params.text_document_position.text_document.uri,
+                lsp::Url::from_file_path("/a/main.rs").unwrap(),
+            );
+            assert_eq!(
+                params.text_document_position.position,
+                lsp::Position::new(0, 14),
+            );
+
+            Ok(Some(vec![lsp::TextEdit {
+                new_text: "~:".to_string(),
+                range: lsp::Range::new(lsp::Position::new(0, 14), lsp::Position::new(0, 14)),
+            }]))
+        })
+        .next()
+        .await
+        .unwrap();
+    cx_a.foreground().finish_waiting();
+
+    // Open the buffer on the host and see that the formattings worked
+    let buffer_a = project_a
+        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+        .await
+        .unwrap();
+    executor.run_until_parked();
+
+    buffer_a.read_with(cx_a, |buffer, _| {
+        assert_eq!(buffer.text(), "fn main() { a:~: }")
+    });
+
+    // Undo should remove LSP edits first
+    editor_b.update(cx_b, |editor, cx| {
+        assert_eq!(editor.text(cx), "fn main() { a:~: }");
+        editor.undo(&Undo, cx);
+        assert_eq!(editor.text(cx), "fn main() { a: }");
+    });
+    executor.run_until_parked();
+
+    buffer_a.read_with(cx_a, |buffer, _| {
+        assert_eq!(buffer.text(), "fn main() { a: }")
+    });
+
+    editor_b.update(cx_b, |editor, cx| {
+        assert_eq!(editor.text(cx), "fn main() { a: }");
+        editor.undo(&Undo, cx);
+        assert_eq!(editor.text(cx), "fn main() { a }");
+    });
+    executor.run_until_parked();
+
+    buffer_a.read_with(cx_a, |buffer, _| {
+        assert_eq!(buffer.text(), "fn main() { a }")
+    });
+}
+
+#[gpui::test(iterations = 10)]
+async fn test_mutual_editor_inlay_hint_cache_update(
+    executor: BackgroundExecutor,
+    cx_a: &mut TestAppContext,
+    cx_b: &mut TestAppContext,
+) {
+    let mut server = TestServer::start(&executor).await;
+    let client_a = server.create_client(cx_a, "user_a").await;
+    let client_b = server.create_client(cx_b, "user_b").await;
+    server
+        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+        .await;
+    let active_call_a = cx_a.read(ActiveCall::global);
+    let active_call_b = cx_b.read(ActiveCall::global);
+
+    cx_a.update(editor::init);
+    cx_b.update(editor::init);
+
+    cx_a.update(|cx| {
+        cx.update_global(|store: &mut SettingsStore, cx| {
+            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+                settings.defaults.inlay_hints = Some(InlayHintSettings {
+                    enabled: true,
+                    show_type_hints: true,
+                    show_parameter_hints: false,
+                    show_other_hints: true,
+                })
+            });
+        });
+    });
+    cx_b.update(|cx| {
+        cx.update_global(|store: &mut SettingsStore, cx| {
+            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+                settings.defaults.inlay_hints = Some(InlayHintSettings {
+                    enabled: true,
+                    show_type_hints: true,
+                    show_parameter_hints: false,
+                    show_other_hints: true,
+                })
+            });
+        });
+    });
+
+    let mut language = Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            path_suffixes: vec!["rs".to_string()],
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    );
+    let mut fake_language_servers = language
+        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+            capabilities: lsp::ServerCapabilities {
+                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
+                ..Default::default()
+            },
+            ..Default::default()
+        }))
+        .await;
+    let language = Arc::new(language);
+    client_a.language_registry().add(Arc::clone(&language));
+    client_b.language_registry().add(language);
+
+    // Client A opens a project.
+    client_a
+        .fs()
+        .insert_tree(
+            "/a",
+            json!({
+                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
+                "other.rs": "// Test file",
+            }),
+        )
+        .await;
+    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+    active_call_a
+        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+        .await
+        .unwrap();
+    let project_id = active_call_a
+        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+        .await
+        .unwrap();
+
+    // Client B joins the project
+    let project_b = client_b.build_remote_project(project_id, cx_b).await;
+    active_call_b
+        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+        .await
+        .unwrap();
+
+    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
+    cx_a.foreground().start_waiting();
+
+    // The host opens a rust file.
+    let _buffer_a = project_a
+        .update(cx_a, |project, cx| {
+            project.open_local_buffer("/a/main.rs", cx)
+        })
+        .await
+        .unwrap();
+    let fake_language_server = fake_language_servers.next().await.unwrap();
+    let editor_a = workspace_a
+        .update(cx_a, |workspace, cx| {
+            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+        })
+        .await
+        .unwrap()
+        .downcast::<Editor>()
+        .unwrap();
+
+    // Set up the language server to return an additional inlay hint on each request.
+    let edits_made = Arc::new(AtomicUsize::new(0));
+    let closure_edits_made = Arc::clone(&edits_made);
+    fake_language_server
+        .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
+            let task_edits_made = Arc::clone(&closure_edits_made);
+            async move {
+                assert_eq!(
+                    params.text_document.uri,
+                    lsp::Url::from_file_path("/a/main.rs").unwrap(),
+                );
+                let edits_made = task_edits_made.load(atomic::Ordering::Acquire);
+                Ok(Some(vec![lsp::InlayHint {
+                    position: lsp::Position::new(0, edits_made as u32),
+                    label: lsp::InlayHintLabel::String(edits_made.to_string()),
+                    kind: None,
+                    text_edits: None,
+                    tooltip: None,
+                    padding_left: None,
+                    padding_right: None,
+                    data: None,
+                }]))
+            }
+        })
+        .next()
+        .await
+        .unwrap();
+
+    executor.run_until_parked();
+
+    let initial_edit = edits_made.load(atomic::Ordering::Acquire);
+    editor_a.update(cx_a, |editor, _| {
+        assert_eq!(
+            vec![initial_edit.to_string()],
+            extract_hint_labels(editor),
+            "Host should get its first hints when opens an editor"
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(
+            inlay_cache.version(),
+            1,
+            "Host editor update the cache version after every cache/view change",
+        );
+    });
+    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
+    let editor_b = workspace_b
+        .update(cx_b, |workspace, cx| {
+            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+        })
+        .await
+        .unwrap()
+        .downcast::<Editor>()
+        .unwrap();
+
+    executor.run_until_parked();
+    editor_b.update(cx_b, |editor, _| {
+        assert_eq!(
+            vec![initial_edit.to_string()],
+            extract_hint_labels(editor),
+            "Client should get its first hints when opens an editor"
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(
+            inlay_cache.version(),
+            1,
+            "Guest editor update the cache version after every cache/view change"
+        );
+    });
+
+    let after_client_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
+    editor_b.update(cx_b, |editor, cx| {
+        editor.change_selections(None, cx, |s| s.select_ranges([13..13].clone()));
+        editor.handle_input(":", cx);
+        cx.focus(&editor_b);
+    });
+
+    executor.run_until_parked();
+    editor_a.update(cx_a, |editor, _| {
+        assert_eq!(
+            vec![after_client_edit.to_string()],
+            extract_hint_labels(editor),
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(inlay_cache.version(), 2);
+    });
+    editor_b.update(cx_b, |editor, _| {
+        assert_eq!(
+            vec![after_client_edit.to_string()],
+            extract_hint_labels(editor),
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(inlay_cache.version(), 2);
+    });
+
+    let after_host_edit = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
+    editor_a.update(cx_a, |editor, cx| {
+        editor.change_selections(None, cx, |s| s.select_ranges([13..13]));
+        editor.handle_input("a change to increment both buffers' versions", cx);
+        cx.focus(&editor_a);
+    });
+
+    executor.run_until_parked();
+    editor_a.update(cx_a, |editor, _| {
+        assert_eq!(
+            vec![after_host_edit.to_string()],
+            extract_hint_labels(editor),
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(inlay_cache.version(), 3);
+    });
+    editor_b.update(cx_b, |editor, _| {
+        assert_eq!(
+            vec![after_host_edit.to_string()],
+            extract_hint_labels(editor),
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(inlay_cache.version(), 3);
+    });
+
+    let after_special_edit_for_refresh = edits_made.fetch_add(1, atomic::Ordering::Release) + 1;
+    fake_language_server
+        .request::<lsp::request::InlayHintRefreshRequest>(())
+        .await
+        .expect("inlay refresh request failed");
+
+    executor.run_until_parked();
+    editor_a.update(cx_a, |editor, _| {
+        assert_eq!(
+            vec![after_special_edit_for_refresh.to_string()],
+            extract_hint_labels(editor),
+            "Host should react to /refresh LSP request"
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(
+            inlay_cache.version(),
+            4,
+            "Host should accepted all edits and bump its cache version every time"
+        );
+    });
+    editor_b.update(cx_b, |editor, _| {
+        assert_eq!(
+            vec![after_special_edit_for_refresh.to_string()],
+            extract_hint_labels(editor),
+            "Guest should get a /refresh LSP request propagated by host"
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(
+            inlay_cache.version(),
+            4,
+            "Guest should accepted all edits and bump its cache version every time"
+        );
+    });
+}
+
+#[gpui::test(iterations = 10)]
+async fn test_inlay_hint_refresh_is_forwarded(
+    executor: BackgroundExecutor,
+    cx_a: &mut TestAppContext,
+    cx_b: &mut TestAppContext,
+) {
+    let mut server = TestServer::start(&executor).await;
+    let client_a = server.create_client(cx_a, "user_a").await;
+    let client_b = server.create_client(cx_b, "user_b").await;
+    server
+        .create_room(&mut [(&client_a, cx_a), (&client_b, cx_b)])
+        .await;
+    let active_call_a = cx_a.read(ActiveCall::global);
+    let active_call_b = cx_b.read(ActiveCall::global);
+
+    cx_a.update(editor::init);
+    cx_b.update(editor::init);
+
+    cx_a.update(|cx| {
+        cx.update_global(|store: &mut SettingsStore, cx| {
+            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+                settings.defaults.inlay_hints = Some(InlayHintSettings {
+                    enabled: false,
+                    show_type_hints: false,
+                    show_parameter_hints: false,
+                    show_other_hints: false,
+                })
+            });
+        });
+    });
+    cx_b.update(|cx| {
+        cx.update_global(|store: &mut SettingsStore, cx| {
+            store.update_user_settings::<AllLanguageSettings>(cx, |settings| {
+                settings.defaults.inlay_hints = Some(InlayHintSettings {
+                    enabled: true,
+                    show_type_hints: true,
+                    show_parameter_hints: true,
+                    show_other_hints: true,
+                })
+            });
+        });
+    });
+
+    let mut language = Language::new(
+        LanguageConfig {
+            name: "Rust".into(),
+            path_suffixes: vec!["rs".to_string()],
+            ..Default::default()
+        },
+        Some(tree_sitter_rust::language()),
+    );
+    let mut fake_language_servers = language
+        .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+            capabilities: lsp::ServerCapabilities {
+                inlay_hint_provider: Some(lsp::OneOf::Left(true)),
+                ..Default::default()
+            },
+            ..Default::default()
+        }))
+        .await;
+    let language = Arc::new(language);
+    client_a.language_registry().add(Arc::clone(&language));
+    client_b.language_registry().add(language);
+
+    client_a
+        .fs()
+        .insert_tree(
+            "/a",
+            json!({
+                "main.rs": "fn main() { a } // and some long comment to ensure inlay hints are not trimmed out",
+                "other.rs": "// Test file",
+            }),
+        )
+        .await;
+    let (project_a, worktree_id) = client_a.build_local_project("/a", cx_a).await;
+    active_call_a
+        .update(cx_a, |call, cx| call.set_location(Some(&project_a), cx))
+        .await
+        .unwrap();
+    let project_id = active_call_a
+        .update(cx_a, |call, cx| call.share_project(project_a.clone(), cx))
+        .await
+        .unwrap();
+
+    let project_b = client_b.build_remote_project(project_id, cx_b).await;
+    active_call_b
+        .update(cx_b, |call, cx| call.set_location(Some(&project_b), cx))
+        .await
+        .unwrap();
+
+    let workspace_a = client_a.build_workspace(&project_a, cx_a).root(cx_a);
+    let workspace_b = client_b.build_workspace(&project_b, cx_b).root(cx_b);
+    cx_a.foreground().start_waiting();
+    cx_b.foreground().start_waiting();
+
+    let editor_a = workspace_a
+        .update(cx_a, |workspace, cx| {
+            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+        })
+        .await
+        .unwrap()
+        .downcast::<Editor>()
+        .unwrap();
+
+    let editor_b = workspace_b
+        .update(cx_b, |workspace, cx| {
+            workspace.open_path((worktree_id, "main.rs"), None, true, cx)
+        })
+        .await
+        .unwrap()
+        .downcast::<Editor>()
+        .unwrap();
+
+    let other_hints = Arc::new(AtomicBool::new(false));
+    let fake_language_server = fake_language_servers.next().await.unwrap();
+    let closure_other_hints = Arc::clone(&other_hints);
+    fake_language_server
+        .handle_request::<lsp::request::InlayHintRequest, _, _>(move |params, _| {
+            let task_other_hints = Arc::clone(&closure_other_hints);
+            async move {
+                assert_eq!(
+                    params.text_document.uri,
+                    lsp::Url::from_file_path("/a/main.rs").unwrap(),
+                );
+                let other_hints = task_other_hints.load(atomic::Ordering::Acquire);
+                let character = if other_hints { 0 } else { 2 };
+                let label = if other_hints {
+                    "other hint"
+                } else {
+                    "initial hint"
+                };
+                Ok(Some(vec![lsp::InlayHint {
+                    position: lsp::Position::new(0, character),
+                    label: lsp::InlayHintLabel::String(label.to_string()),
+                    kind: None,
+                    text_edits: None,
+                    tooltip: None,
+                    padding_left: None,
+                    padding_right: None,
+                    data: None,
+                }]))
+            }
+        })
+        .next()
+        .await
+        .unwrap();
+    cx_a.foreground().finish_waiting();
+    cx_b.foreground().finish_waiting();
+
+    executor.run_until_parked();
+    editor_a.update(cx_a, |editor, _| {
+        assert!(
+            extract_hint_labels(editor).is_empty(),
+            "Host should get no hints due to them turned off"
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(
+            inlay_cache.version(),
+            0,
+            "Turned off hints should not generate version updates"
+        );
+    });
+
+    executor.run_until_parked();
+    editor_b.update(cx_b, |editor, _| {
+        assert_eq!(
+            vec!["initial hint".to_string()],
+            extract_hint_labels(editor),
+            "Client should get its first hints when opens an editor"
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(
+            inlay_cache.version(),
+            1,
+            "Should update cache verison after first hints"
+        );
+    });
+
+    other_hints.fetch_or(true, atomic::Ordering::Release);
+    fake_language_server
+        .request::<lsp::request::InlayHintRefreshRequest>(())
+        .await
+        .expect("inlay refresh request failed");
+    executor.run_until_parked();
+    editor_a.update(cx_a, |editor, _| {
+        assert!(
+            extract_hint_labels(editor).is_empty(),
+            "Host should get nop hints due to them turned off, even after the /refresh"
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(
+            inlay_cache.version(),
+            0,
+            "Turned off hints should not generate version updates, again"
+        );
+    });
+
+    executor.run_until_parked();
+    editor_b.update(cx_b, |editor, _| {
+        assert_eq!(
+            vec!["other hint".to_string()],
+            extract_hint_labels(editor),
+            "Guest should get a /refresh LSP request propagated by host despite host hints are off"
+        );
+        let inlay_cache = editor.inlay_hint_cache();
+        assert_eq!(
+            inlay_cache.version(),
+            2,
+            "Guest should accepted all edits and bump its cache version every time"
+        );
+    });
+}
+
+fn extract_hint_labels(editor: &Editor) -> Vec<String> {
+    let mut labels = Vec::new();
+    for hint in editor.inlay_hint_cache().hints() {
+        match hint.label {
+            project::InlayHintLabel::String(s) => labels.push(s),
+            _ => unreachable!(),
+        }
+    }
+    labels
+}

crates/editor2/src/editor_tests.rs 🔗

@@ -10,9 +10,6 @@ use crate::{
 use drag_and_drop::DragAndDrop;
 use futures::StreamExt;
 use gpui::{
-    executor::Deterministic,
-    geometry::{rect::RectF, vector::vec2f},
-    platform::{WindowBounds, WindowOptions},
     serde_json::{self, json},
     TestAppContext,
 };
@@ -42,8 +39,8 @@ use workspace::{
 fn test_edit_events(cx: &mut TestAppContext) {
     init_test(cx, |_| {});
 
-    let buffer = cx.add_model(|cx| {
-        let mut buffer = language::Buffer::new(0, cx.model_id() as u64, "123456");
+    let buffer = cx.build_model(|cx| {
+        let mut buffer = language::Buffer::new(0, cx.entity_id().as_u64(), "123456");
         buffer.set_group_interval(Duration::from_secs(1));
         buffer
     });
@@ -53,11 +50,8 @@ fn test_edit_events(cx: &mut TestAppContext) {
         .add_window({
             let events = events.clone();
             |cx| {
-                cx.subscribe(&cx.handle(), move |_, _, event, _| {
-                    if matches!(
-                        event,
-                        Event::Edited | Event::BufferEdited | Event::DirtyChanged
-                    ) {
+                cx.subscribe(cx.view(), move |_, _, event, _| {
+                    if matches!(event, Event::Edited | Event::BufferEdited) {
                         events.borrow_mut().push(("editor1", event.clone()));
                     }
                 })
@@ -65,16 +59,14 @@ fn test_edit_events(cx: &mut TestAppContext) {
                 Editor::for_buffer(buffer.clone(), None, cx)
             }
         })
-        .root(cx);
+        .root(cx)
+        .unwrap();
     let editor2 = cx
         .add_window({
             let events = events.clone();
             |cx| {
-                cx.subscribe(&cx.handle(), move |_, _, event, _| {
-                    if matches!(
-                        event,
-                        Event::Edited | Event::BufferEdited | Event::DirtyChanged
-                    ) {
+                cx.subscribe(cx.view(), move |_, _, event, _| {
+                    if matches!(event, Event::Edited | Event::BufferEdited) {
                         events.borrow_mut().push(("editor2", event.clone()));
                     }
                 })
@@ -82,7 +74,8 @@ fn test_edit_events(cx: &mut TestAppContext) {
                 Editor::for_buffer(buffer.clone(), None, cx)
             }
         })
-        .root(cx);
+        .root(cx)
+        .unwrap();
     assert_eq!(mem::take(&mut *events.borrow_mut()), []);
 
     // Mutating editor 1 will emit an `Edited` event only for that editor.
@@ -93,8 +86,6 @@ fn test_edit_events(cx: &mut TestAppContext) {
             ("editor1", Event::Edited),
             ("editor1", Event::BufferEdited),
             ("editor2", Event::BufferEdited),
-            ("editor1", Event::DirtyChanged),
-            ("editor2", Event::DirtyChanged)
         ]
     );
 
@@ -365,7 +356,7 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
     );
 
     editor.update(cx, |view, cx| {
-        view.update_selection(DisplayPoint::new(3, 3), 0, Point<Pixels>::zero(), cx);
+        view.update_selection(DisplayPoint::new(3, 3), 0, Point::<Pixels>::zero(), cx);
     });
 
     assert_eq!(
@@ -374,7 +365,7 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
     );
 
     editor.update(cx, |view, cx| {
-        view.update_selection(DisplayPoint::new(1, 1), 0, Point<Pixels>::zero(), cx);
+        view.update_selection(DisplayPoint::new(1, 1), 0, Point::<Pixels>::zero(), cx);
     });
 
     assert_eq!(
@@ -384,7 +375,7 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
 
     editor.update(cx, |view, cx| {
         view.end_selection(cx);
-        view.update_selection(DisplayPoint::new(3, 3), 0, Point<Pixels>::zero(), cx);
+        view.update_selection(DisplayPoint::new(3, 3), 0, Point::<Pixels>::zero(), cx);
     });
 
     assert_eq!(
@@ -394,7 +385,7 @@ fn test_selection_with_mouse(cx: &mut TestAppContext) {
 
     editor.update(cx, |view, cx| {
         view.begin_selection(DisplayPoint::new(3, 3), true, 1, cx);
-        view.update_selection(DisplayPoint::new(0, 0), 0, Point<Pixels>::zero(), cx);
+        view.update_selection(DisplayPoint::new(0, 0), 0, Point::<Pixels>::zero(), cx);
     });
 
     assert_eq!(
@@ -435,7 +426,7 @@ fn test_canceling_pending_selection(cx: &mut TestAppContext) {
     });
 
     view.update(cx, |view, cx| {
-        view.update_selection(DisplayPoint::new(3, 3), 0, Point<Pixels>::zero(), cx);
+        view.update_selection(DisplayPoint::new(3, 3), 0, Point::<Pixels>::zero(), cx);
         assert_eq!(
             view.selections.display_ranges(cx),
             [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
@@ -444,7 +435,7 @@ fn test_canceling_pending_selection(cx: &mut TestAppContext) {
 
     view.update(cx, |view, cx| {
         view.cancel(&Cancel, cx);
-        view.update_selection(DisplayPoint::new(1, 1), 0, Point<Pixels>::zero(), cx);
+        view.update_selection(DisplayPoint::new(1, 1), 0, Point::<Pixels>::zero(), cx);
         assert_eq!(
             view.selections.display_ranges(cx),
             [DisplayPoint::new(2, 2)..DisplayPoint::new(3, 3)]
@@ -589,12 +580,12 @@ async fn test_navigation_history(cx: &mut TestAppContext) {
         assert!(pop_history(&mut editor, cx).is_none());
 
         // Set scroll position to check later
-        editor.set_scroll_position(Point<Pixels>::new(5.5, 5.5), cx);
+        editor.set_scroll_position(Point::<Pixels>::new(5.5, 5.5), cx);
         let original_scroll_position = editor.scroll_manager.anchor();
 
         // Jump to the end of the document and adjust scroll
         editor.move_to_end(&MoveToEnd, cx);
-        editor.set_scroll_position(Point<Pixels>::new(-2.5, -0.5), cx);
+        editor.set_scroll_position(Point::<Pixels>::new(-2.5, -0.5), cx);
         assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
 
         let nav_entry = pop_history(&mut editor, cx).unwrap();
@@ -643,11 +634,11 @@ fn test_cancel(cx: &mut TestAppContext) {
 
     view.update(cx, |view, cx| {
         view.begin_selection(DisplayPoint::new(3, 4), false, 1, cx);
-        view.update_selection(DisplayPoint::new(1, 1), 0, Point<Pixels>::zero(), cx);
+        view.update_selection(DisplayPoint::new(1, 1), 0, Point::<Pixels>::zero(), cx);
         view.end_selection(cx);
 
         view.begin_selection(DisplayPoint::new(0, 1), true, 1, cx);
-        view.update_selection(DisplayPoint::new(0, 3), 0, Point<Pixels>::zero(), cx);
+        view.update_selection(DisplayPoint::new(0, 3), 0, Point::<Pixels>::zero(), cx);
         view.end_selection(cx);
         assert_eq!(
             view.selections.display_ranges(cx),

crates/editor2/src/test.rs 🔗

@@ -60,7 +60,7 @@ pub fn assert_text_with_selections(
 #[allow(dead_code)]
 #[cfg(any(test, feature = "test-support"))]
 pub(crate) fn build_editor(buffer: Model<MultiBuffer>, cx: &mut ViewContext<Editor>) -> Editor {
-    Editor::new(EditorMode::Full, buffer, None, None, cx)
+    Editor::new(EditorMode::Full, buffer, None, /*None,*/ cx)
 }
 
 pub(crate) fn build_editor_with_project(
@@ -68,5 +68,5 @@ pub(crate) fn build_editor_with_project(
     buffer: Model<MultiBuffer>,
     cx: &mut ViewContext<Editor>,
 ) -> Editor {
-    Editor::new(EditorMode::Full, buffer, Some(project), None, cx)
+    Editor::new(EditorMode::Full, buffer, Some(project), /*None,*/ cx)
 }

crates/editor2/src/test/editor_lsp_test_context.rs 🔗

@@ -1,297 +1,297 @@
-// use std::{
-//     borrow::Cow,
-//     ops::{Deref, DerefMut, Range},
-//     sync::Arc,
-// };
-
-// use anyhow::Result;
-
-// use crate::{Editor, ToPoint};
-// use collections::HashSet;
-// use futures::Future;
-// use gpui::{json, View, ViewContext};
-// use indoc::indoc;
-// use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries};
-// use lsp::{notification, request};
-// use multi_buffer::ToPointUtf16;
-// use project::Project;
-// use smol::stream::StreamExt;
-// use workspace::{AppState, Workspace, WorkspaceHandle};
-
-// use super::editor_test_context::EditorTestContext;
-
-// pub struct EditorLspTestContext<'a> {
-//     pub cx: EditorTestContext<'a>,
-//     pub lsp: lsp::FakeLanguageServer,
-//     pub workspace: View<Workspace>,
-//     pub buffer_lsp_url: lsp::Url,
-// }
-
-// impl<'a> EditorLspTestContext<'a> {
-//     pub async fn new(
-//         mut language: Language,
-//         capabilities: lsp::ServerCapabilities,
-//         cx: &'a mut gpui::TestAppContext,
-//     ) -> EditorLspTestContext<'a> {
-//         use json::json;
-
-//         let app_state = cx.update(AppState::test);
-
-//         cx.update(|cx| {
-//             language::init(cx);
-//             crate::init(cx);
-//             workspace::init(app_state.clone(), cx);
-//             Project::init_settings(cx);
-//         });
-
-//         let file_name = format!(
-//             "file.{}",
-//             language
-//                 .path_suffixes()
-//                 .first()
-//                 .expect("language must have a path suffix for EditorLspTestContext")
-//         );
-
-//         let mut fake_servers = language
-//             .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
-//                 capabilities,
-//                 ..Default::default()
-//             }))
-//             .await;
-
-//         let project = Project::test(app_state.fs.clone(), [], cx).await;
-//         project.update(cx, |project, _| project.languages().add(Arc::new(language)));
-
-//         app_state
-//             .fs
-//             .as_fake()
-//             .insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
-//             .await;
-
-//         let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
-//         let workspace = window.root(cx);
-//         project
-//             .update(cx, |project, cx| {
-//                 project.find_or_create_local_worktree("/root", true, cx)
-//             })
-//             .await
-//             .unwrap();
-//         cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
-//             .await;
-
-//         let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
-//         let item = workspace
-//             .update(cx, |workspace, cx| {
-//                 workspace.open_path(file, None, true, cx)
-//             })
-//             .await
-//             .expect("Could not open test file");
-
-//         let editor = cx.update(|cx| {
-//             item.act_as::<Editor>(cx)
-//                 .expect("Opened test file wasn't an editor")
-//         });
-//         editor.update(cx, |_, cx| cx.focus_self());
-
-//         let lsp = fake_servers.next().await.unwrap();
-
-//         Self {
-//             cx: EditorTestContext {
-//                 cx,
-//                 window: window.into(),
-//                 editor,
-//             },
-//             lsp,
-//             workspace,
-//             buffer_lsp_url: lsp::Url::from_file_path(format!("/root/dir/{file_name}")).unwrap(),
-//         }
-//     }
-
-//     pub async fn new_rust(
-//         capabilities: lsp::ServerCapabilities,
-//         cx: &'a mut gpui::TestAppContext,
-//     ) -> EditorLspTestContext<'a> {
-//         let language = Language::new(
-//             LanguageConfig {
-//                 name: "Rust".into(),
-//                 path_suffixes: vec!["rs".to_string()],
-//                 ..Default::default()
-//             },
-//             Some(tree_sitter_rust::language()),
-//         )
-//         .with_queries(LanguageQueries {
-//             indents: Some(Cow::from(indoc! {r#"
-//                 [
-//                     ((where_clause) _ @end)
-//                     (field_expression)
-//                     (call_expression)
-//                     (assignment_expression)
-//                     (let_declaration)
-//                     (let_chain)
-//                     (await_expression)
-//                 ] @indent
-
-//                 (_ "[" "]" @end) @indent
-//                 (_ "<" ">" @end) @indent
-//                 (_ "{" "}" @end) @indent
-//                 (_ "(" ")" @end) @indent"#})),
-//             brackets: Some(Cow::from(indoc! {r#"
-//                 ("(" @open ")" @close)
-//                 ("[" @open "]" @close)
-//                 ("{" @open "}" @close)
-//                 ("<" @open ">" @close)
-//                 ("\"" @open "\"" @close)
-//                 (closure_parameters "|" @open "|" @close)"#})),
-//             ..Default::default()
-//         })
-//         .expect("Could not parse queries");
-
-//         Self::new(language, capabilities, cx).await
-//     }
-
-//     pub async fn new_typescript(
-//         capabilities: lsp::ServerCapabilities,
-//         cx: &'a mut gpui::TestAppContext,
-//     ) -> EditorLspTestContext<'a> {
-//         let mut word_characters: HashSet<char> = Default::default();
-//         word_characters.insert('$');
-//         word_characters.insert('#');
-//         let language = Language::new(
-//             LanguageConfig {
-//                 name: "Typescript".into(),
-//                 path_suffixes: vec!["ts".to_string()],
-//                 brackets: language::BracketPairConfig {
-//                     pairs: vec![language::BracketPair {
-//                         start: "{".to_string(),
-//                         end: "}".to_string(),
-//                         close: true,
-//                         newline: true,
-//                     }],
-//                     disabled_scopes_by_bracket_ix: Default::default(),
-//                 },
-//                 word_characters,
-//                 ..Default::default()
-//             },
-//             Some(tree_sitter_typescript::language_typescript()),
-//         )
-//         .with_queries(LanguageQueries {
-//             brackets: Some(Cow::from(indoc! {r#"
-//                 ("(" @open ")" @close)
-//                 ("[" @open "]" @close)
-//                 ("{" @open "}" @close)
-//                 ("<" @open ">" @close)
-//                 ("\"" @open "\"" @close)"#})),
-//             indents: Some(Cow::from(indoc! {r#"
-//                 [
-//                     (call_expression)
-//                     (assignment_expression)
-//                     (member_expression)
-//                     (lexical_declaration)
-//                     (variable_declaration)
-//                     (assignment_expression)
-//                     (if_statement)
-//                     (for_statement)
-//                 ] @indent
-
-//                 (_ "[" "]" @end) @indent
-//                 (_ "<" ">" @end) @indent
-//                 (_ "{" "}" @end) @indent
-//                 (_ "(" ")" @end) @indent
-//                 "#})),
-//             ..Default::default()
-//         })
-//         .expect("Could not parse queries");
-
-//         Self::new(language, capabilities, cx).await
-//     }
-
-//     // Constructs lsp range using a marked string with '[', ']' range delimiters
-//     pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
-//         let ranges = self.ranges(marked_text);
-//         self.to_lsp_range(ranges[0].clone())
-//     }
-
-//     pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {
-//         let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
-//         let start_point = range.start.to_point(&snapshot.buffer_snapshot);
-//         let end_point = range.end.to_point(&snapshot.buffer_snapshot);
-
-//         self.editor(|editor, cx| {
-//             let buffer = editor.buffer().read(cx);
-//             let start = point_to_lsp(
-//                 buffer
-//                     .point_to_buffer_offset(start_point, cx)
-//                     .unwrap()
-//                     .1
-//                     .to_point_utf16(&buffer.read(cx)),
-//             );
-//             let end = point_to_lsp(
-//                 buffer
-//                     .point_to_buffer_offset(end_point, cx)
-//                     .unwrap()
-//                     .1
-//                     .to_point_utf16(&buffer.read(cx)),
-//             );
-
-//             lsp::Range { start, end }
-//         })
-//     }
-
-//     pub fn to_lsp(&mut self, offset: usize) -> lsp::Position {
-//         let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
-//         let point = offset.to_point(&snapshot.buffer_snapshot);
-
-//         self.editor(|editor, cx| {
-//             let buffer = editor.buffer().read(cx);
-//             point_to_lsp(
-//                 buffer
-//                     .point_to_buffer_offset(point, cx)
-//                     .unwrap()
-//                     .1
-//                     .to_point_utf16(&buffer.read(cx)),
-//             )
-//         })
-//     }
-
-//     pub fn update_workspace<F, T>(&mut self, update: F) -> T
-//     where
-//         F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
-//     {
-//         self.workspace.update(self.cx.cx, update)
-//     }
-
-//     pub fn handle_request<T, F, Fut>(
-//         &self,
-//         mut handler: F,
-//     ) -> futures::channel::mpsc::UnboundedReceiver<()>
-//     where
-//         T: 'static + request::Request,
-//         T::Params: 'static + Send,
-//         F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
-//         Fut: 'static + Send + Future<Output = Result<T::Result>>,
-//     {
-//         let url = self.buffer_lsp_url.clone();
-//         self.lsp.handle_request::<T, _, _>(move |params, cx| {
-//             let url = url.clone();
-//             handler(url, params, cx)
-//         })
-//     }
-
-//     pub fn notify<T: notification::Notification>(&self, params: T::Params) {
-//         self.lsp.notify::<T>(params);
-//     }
-// }
-
-// impl<'a> Deref for EditorLspTestContext<'a> {
-//     type Target = EditorTestContext<'a>;
-
-//     fn deref(&self) -> &Self::Target {
-//         &self.cx
-//     }
-// }
-
-// impl<'a> DerefMut for EditorLspTestContext<'a> {
-//     fn deref_mut(&mut self) -> &mut Self::Target {
-//         &mut self.cx
-//     }
-// }
+use std::{
+    borrow::Cow,
+    ops::{Deref, DerefMut, Range},
+    sync::Arc,
+};
+
+use anyhow::Result;
+
+use crate::{Editor, ToPoint};
+use collections::HashSet;
+use futures::Future;
+use gpui::{json, View, ViewContext};
+use indoc::indoc;
+use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries};
+use lsp::{notification, request};
+use multi_buffer::ToPointUtf16;
+use project::Project;
+use smol::stream::StreamExt;
+use workspace::{AppState, Workspace, WorkspaceHandle};
+
+use super::editor_test_context::EditorTestContext;
+
+pub struct EditorLspTestContext<'a> {
+    pub cx: EditorTestContext<'a>,
+    pub lsp: lsp::FakeLanguageServer,
+    pub workspace: View<Workspace>,
+    pub buffer_lsp_url: lsp::Url,
+}
+
+impl<'a> EditorLspTestContext<'a> {
+    pub async fn new(
+        mut language: Language,
+        capabilities: lsp::ServerCapabilities,
+        cx: &'a mut gpui::TestAppContext,
+    ) -> EditorLspTestContext<'a> {
+        use json::json;
+
+        let app_state = cx.update(AppState::test);
+
+        cx.update(|cx| {
+            language::init(cx);
+            crate::init(cx);
+            workspace::init(app_state.clone(), cx);
+            Project::init_settings(cx);
+        });
+
+        let file_name = format!(
+            "file.{}",
+            language
+                .path_suffixes()
+                .first()
+                .expect("language must have a path suffix for EditorLspTestContext")
+        );
+
+        let mut fake_servers = language
+            .set_fake_lsp_adapter(Arc::new(FakeLspAdapter {
+                capabilities,
+                ..Default::default()
+            }))
+            .await;
+
+        let project = Project::test(app_state.fs.clone(), [], cx).await;
+        project.update(cx, |project, _| project.languages().add(Arc::new(language)));
+
+        app_state
+            .fs
+            .as_fake()
+            .insert_tree("/root", json!({ "dir": { file_name.clone(): "" }}))
+            .await;
+
+        let window = cx.add_window(|cx| Workspace::test_new(project.clone(), cx));
+        let workspace = window.root(cx);
+        project
+            .update(cx, |project, cx| {
+                project.find_or_create_local_worktree("/root", true, cx)
+            })
+            .await
+            .unwrap();
+        cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
+            .await;
+
+        let file = cx.read(|cx| workspace.file_project_paths(cx)[0].clone());
+        let item = workspace
+            .update(cx, |workspace, cx| {
+                workspace.open_path(file, None, true, cx)
+            })
+            .await
+            .expect("Could not open test file");
+
+        let editor = cx.update(|cx| {
+            item.act_as::<Editor>(cx)
+                .expect("Opened test file wasn't an editor")
+        });
+        editor.update(cx, |_, cx| cx.focus_self());
+
+        let lsp = fake_servers.next().await.unwrap();
+
+        Self {
+            cx: EditorTestContext {
+                cx,
+                window: window.into(),
+                editor,
+            },
+            lsp,
+            workspace,
+            buffer_lsp_url: lsp::Url::from_file_path(format!("/root/dir/{file_name}")).unwrap(),
+        }
+    }
+
+    pub async fn new_rust(
+        capabilities: lsp::ServerCapabilities,
+        cx: &'a mut gpui::TestAppContext,
+    ) -> EditorLspTestContext<'a> {
+        let language = Language::new(
+            LanguageConfig {
+                name: "Rust".into(),
+                path_suffixes: vec!["rs".to_string()],
+                ..Default::default()
+            },
+            Some(tree_sitter_rust::language()),
+        )
+        .with_queries(LanguageQueries {
+            indents: Some(Cow::from(indoc! {r#"
+                [
+                    ((where_clause) _ @end)
+                    (field_expression)
+                    (call_expression)
+                    (assignment_expression)
+                    (let_declaration)
+                    (let_chain)
+                    (await_expression)
+                ] @indent
+
+                (_ "[" "]" @end) @indent
+                (_ "<" ">" @end) @indent
+                (_ "{" "}" @end) @indent
+                (_ "(" ")" @end) @indent"#})),
+            brackets: Some(Cow::from(indoc! {r#"
+                ("(" @open ")" @close)
+                ("[" @open "]" @close)
+                ("{" @open "}" @close)
+                ("<" @open ">" @close)
+                ("\"" @open "\"" @close)
+                (closure_parameters "|" @open "|" @close)"#})),
+            ..Default::default()
+        })
+        .expect("Could not parse queries");
+
+        Self::new(language, capabilities, cx).await
+    }
+
+    pub async fn new_typescript(
+        capabilities: lsp::ServerCapabilities,
+        cx: &'a mut gpui::TestAppContext,
+    ) -> EditorLspTestContext<'a> {
+        let mut word_characters: HashSet<char> = Default::default();
+        word_characters.insert('$');
+        word_characters.insert('#');
+        let language = Language::new(
+            LanguageConfig {
+                name: "Typescript".into(),
+                path_suffixes: vec!["ts".to_string()],
+                brackets: language::BracketPairConfig {
+                    pairs: vec![language::BracketPair {
+                        start: "{".to_string(),
+                        end: "}".to_string(),
+                        close: true,
+                        newline: true,
+                    }],
+                    disabled_scopes_by_bracket_ix: Default::default(),
+                },
+                word_characters,
+                ..Default::default()
+            },
+            Some(tree_sitter_typescript::language_typescript()),
+        )
+        .with_queries(LanguageQueries {
+            brackets: Some(Cow::from(indoc! {r#"
+                ("(" @open ")" @close)
+                ("[" @open "]" @close)
+                ("{" @open "}" @close)
+                ("<" @open ">" @close)
+                ("\"" @open "\"" @close)"#})),
+            indents: Some(Cow::from(indoc! {r#"
+                [
+                    (call_expression)
+                    (assignment_expression)
+                    (member_expression)
+                    (lexical_declaration)
+                    (variable_declaration)
+                    (assignment_expression)
+                    (if_statement)
+                    (for_statement)
+                ] @indent
+
+                (_ "[" "]" @end) @indent
+                (_ "<" ">" @end) @indent
+                (_ "{" "}" @end) @indent
+                (_ "(" ")" @end) @indent
+                "#})),
+            ..Default::default()
+        })
+        .expect("Could not parse queries");
+
+        Self::new(language, capabilities, cx).await
+    }
+
+    // Constructs lsp range using a marked string with '[', ']' range delimiters
+    pub fn lsp_range(&mut self, marked_text: &str) -> lsp::Range {
+        let ranges = self.ranges(marked_text);
+        self.to_lsp_range(ranges[0].clone())
+    }
+
+    pub fn to_lsp_range(&mut self, range: Range<usize>) -> lsp::Range {
+        let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
+        let start_point = range.start.to_point(&snapshot.buffer_snapshot);
+        let end_point = range.end.to_point(&snapshot.buffer_snapshot);
+
+        self.editor(|editor, cx| {
+            let buffer = editor.buffer().read(cx);
+            let start = point_to_lsp(
+                buffer
+                    .point_to_buffer_offset(start_point, cx)
+                    .unwrap()
+                    .1
+                    .to_point_utf16(&buffer.read(cx)),
+            );
+            let end = point_to_lsp(
+                buffer
+                    .point_to_buffer_offset(end_point, cx)
+                    .unwrap()
+                    .1
+                    .to_point_utf16(&buffer.read(cx)),
+            );
+
+            lsp::Range { start, end }
+        })
+    }
+
+    pub fn to_lsp(&mut self, offset: usize) -> lsp::Position {
+        let snapshot = self.update_editor(|editor, cx| editor.snapshot(cx));
+        let point = offset.to_point(&snapshot.buffer_snapshot);
+
+        self.editor(|editor, cx| {
+            let buffer = editor.buffer().read(cx);
+            point_to_lsp(
+                buffer
+                    .point_to_buffer_offset(point, cx)
+                    .unwrap()
+                    .1
+                    .to_point_utf16(&buffer.read(cx)),
+            )
+        })
+    }
+
+    pub fn update_workspace<F, T>(&mut self, update: F) -> T
+    where
+        F: FnOnce(&mut Workspace, &mut ViewContext<Workspace>) -> T,
+    {
+        self.workspace.update(self.cx.cx, update)
+    }
+
+    pub fn handle_request<T, F, Fut>(
+        &self,
+        mut handler: F,
+    ) -> futures::channel::mpsc::UnboundedReceiver<()>
+    where
+        T: 'static + request::Request,
+        T::Params: 'static + Send,
+        F: 'static + Send + FnMut(lsp::Url, T::Params, gpui::AsyncAppContext) -> Fut,
+        Fut: 'static + Send + Future<Output = Result<T::Result>>,
+    {
+        let url = self.buffer_lsp_url.clone();
+        self.lsp.handle_request::<T, _, _>(move |params, cx| {
+            let url = url.clone();
+            handler(url, params, cx)
+        })
+    }
+
+    pub fn notify<T: notification::Notification>(&self, params: T::Params) {
+        self.lsp.notify::<T>(params);
+    }
+}
+
+impl<'a> Deref for EditorLspTestContext<'a> {
+    type Target = EditorTestContext<'a>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.cx
+    }
+}
+
+impl<'a> DerefMut for EditorLspTestContext<'a> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.cx
+    }
+}

crates/zed2/src/main.rs 🔗

@@ -357,8 +357,7 @@ async fn restore_or_create_workspace(app_state: &Arc<AppState>, mut cx: AsyncApp
         } else {
             cx.update(|cx| {
                 workspace::open_new(app_state, cx, |workspace, cx| {
-                    // todo!(editor)
-                    // Editor::new_file(workspace, &Default::default(), cx)
+                    Editor::new_file(workspace, &Default::default(), cx)
                 })
                 .detach();
             })?;