Merge pull request #1217 from zed-industries/fix-lsp-statuses-for-guests

Max Brunsfeld created

Report running language servers when sharing project for the first time

Change summary

crates/collab/src/integration_tests.rs | 104 ++++++++++++++++++++++++++++
crates/language/src/language.rs        |   3 
crates/lsp/src/lsp.rs                  |  15 ++-
crates/project/src/project.rs          |  12 +++
4 files changed, 127 insertions(+), 7 deletions(-)

Detailed changes

crates/collab/src/integration_tests.rs 🔗

@@ -2944,6 +2944,110 @@ async fn test_collaborating_with_renames(cx_a: &mut TestAppContext, cx_b: &mut T
     })
 }
 
+#[gpui::test(iterations = 10)]
+async fn test_language_server_statuses(
+    deterministic: Arc<Deterministic>,
+    cx_a: &mut TestAppContext,
+    cx_b: &mut TestAppContext,
+) {
+    deterministic.forbid_parking();
+
+    cx_b.update(|cx| editor::init(cx));
+    let mut server = TestServer::start(cx_a.foreground(), cx_a.background()).await;
+    let client_a = server.create_client(cx_a, "user_a").await;
+    let client_b = server.create_client(cx_b, "user_b").await;
+    server
+        .make_contacts(vec![(&client_a, cx_a), (&client_b, cx_b)])
+        .await;
+
+    // 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(FakeLspAdapter {
+        name: "the-language-server",
+        ..Default::default()
+    });
+    client_a.language_registry.add(Arc::new(language));
+
+    client_a
+        .fs
+        .insert_tree(
+            "/dir",
+            json!({
+                "main.rs": "const ONE: usize = 1;",
+            }),
+        )
+        .await;
+    let (project_a, worktree_id) = client_a.build_local_project("/dir", cx_a).await;
+
+    let _buffer_a = project_a
+        .update(cx_a, |p, cx| p.open_buffer((worktree_id, "main.rs"), cx))
+        .await
+        .unwrap();
+
+    let fake_language_server = fake_language_servers.next().await.unwrap();
+    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
+        token: lsp::NumberOrString::String("the-token".to_string()),
+        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
+            lsp::WorkDoneProgressReport {
+                message: Some("the-message".to_string()),
+                ..Default::default()
+            },
+        )),
+    });
+    deterministic.run_until_parked();
+    project_a.read_with(cx_a, |project, _| {
+        let status = project.language_server_statuses().next().unwrap();
+        assert_eq!(status.name, "the-language-server");
+        assert_eq!(status.pending_work.len(), 1);
+        assert_eq!(
+            status.pending_work["the-token"].message.as_ref().unwrap(),
+            "the-message"
+        );
+    });
+
+    let project_b = client_b.build_remote_project(&project_a, cx_a, cx_b).await;
+    project_b.read_with(cx_b, |project, _| {
+        let status = project.language_server_statuses().next().unwrap();
+        assert_eq!(status.name, "the-language-server");
+    });
+
+    fake_language_server.notify::<lsp::notification::Progress>(lsp::ProgressParams {
+        token: lsp::NumberOrString::String("the-token".to_string()),
+        value: lsp::ProgressParamsValue::WorkDone(lsp::WorkDoneProgress::Report(
+            lsp::WorkDoneProgressReport {
+                message: Some("the-message-2".to_string()),
+                ..Default::default()
+            },
+        )),
+    });
+    deterministic.run_until_parked();
+    project_a.read_with(cx_a, |project, _| {
+        let status = project.language_server_statuses().next().unwrap();
+        assert_eq!(status.name, "the-language-server");
+        assert_eq!(status.pending_work.len(), 1);
+        assert_eq!(
+            status.pending_work["the-token"].message.as_ref().unwrap(),
+            "the-message-2"
+        );
+    });
+    project_b.read_with(cx_b, |project, _| {
+        let status = project.language_server_statuses().next().unwrap();
+        assert_eq!(status.name, "the-language-server");
+        assert_eq!(status.pending_work.len(), 1);
+        assert_eq!(
+            status.pending_work["the-token"].message.as_ref().unwrap(),
+            "the-message-2"
+        );
+    });
+}
+
 #[gpui::test(iterations = 10)]
 async fn test_basic_chat(cx_a: &mut TestAppContext, cx_b: &mut TestAppContext) {
     cx_a.foreground().forbid_parking();

crates/language/src/language.rs 🔗

@@ -284,7 +284,8 @@ impl LanguageRegistry {
             let language = language.clone();
             return Some(cx.spawn(|cx| async move {
                 let (servers_tx, fake_adapter) = language.fake_adapter.as_ref().unwrap();
-                let (server, mut fake_server) = lsp::LanguageServer::fake_with_capabilities(
+                let (server, mut fake_server) = lsp::LanguageServer::fake(
+                    fake_adapter.name.to_string(),
                     fake_adapter.capabilities.clone(),
                     cx.clone(),
                 );

crates/lsp/src/lsp.rs 🔗

@@ -592,11 +592,8 @@ impl LanguageServer {
         }
     }
 
-    pub fn fake(cx: AsyncAppContext) -> (Self, FakeLanguageServer) {
-        Self::fake_with_capabilities(Self::full_capabilities(), cx)
-    }
-
-    pub fn fake_with_capabilities(
+    pub fn fake(
+        name: String,
         capabilities: ServerCapabilities,
         cx: AsyncAppContext,
     ) -> (Self, FakeLanguageServer) {
@@ -631,9 +628,14 @@ impl LanguageServer {
             let capabilities = capabilities.clone();
             move |_, _| {
                 let capabilities = capabilities.clone();
+                let name = name.clone();
                 async move {
                     Ok(InitializeResult {
                         capabilities,
+                        server_info: Some(ServerInfo {
+                            name,
+                            ..Default::default()
+                        }),
                         ..Default::default()
                     })
                 }
@@ -740,7 +742,8 @@ mod tests {
 
     #[gpui::test]
     async fn test_fake(cx: &mut TestAppContext) {
-        let (server, mut fake) = LanguageServer::fake(cx.to_async());
+        let (server, mut fake) =
+            LanguageServer::fake("the-lsp".to_string(), Default::default(), cx.to_async());
 
         let (message_tx, message_rx) = channel::unbounded();
         let (diagnostics_tx, diagnostics_rx) = channel::unbounded();

crates/project/src/project.rs 🔗

@@ -1256,6 +1256,18 @@ impl Project {
             });
         }
 
+        for (server_id, status) in &self.language_server_statuses {
+            self.client
+                .send(proto::StartLanguageServer {
+                    project_id,
+                    server: Some(proto::LanguageServer {
+                        id: *server_id as u64,
+                        name: status.name.clone(),
+                    }),
+                })
+                .log_err();
+        }
+
         cx.spawn(|this, mut cx| async move {
             for task in tasks {
                 task.await?;