lsp: Use available workspace folders in initialize params (#25753)

Piotr Osiewicz created

Closes https://github.com/zed-industries/zed/issues/25743
Closes https://github.com/biomejs/biome-zed/issues/73

Release Notes:

- Fixed issues with launching Svelte/Biome language servers

Change summary

crates/copilot/src/copilot.rs   |  1 +
crates/languages/src/go.rs      | 10 +---------
crates/lsp/src/lsp.rs           | 32 ++++++++++++++++++++++++++------
crates/prettier/src/prettier.rs |  1 +
crates/project/src/lsp_store.rs |  9 +++++----
5 files changed, 34 insertions(+), 19 deletions(-)

Detailed changes

crates/languages/src/go.rs 🔗

@@ -5,7 +5,7 @@ use futures::StreamExt;
 use gpui::{App, AsyncApp, Task};
 use http_client::github::latest_github_release;
 pub use language::*;
-use lsp::{InitializeParams, LanguageServerBinary, LanguageServerName};
+use lsp::{LanguageServerBinary, LanguageServerName};
 use project::Fs;
 use regex::Regex;
 use serde_json::json;
@@ -373,14 +373,6 @@ impl super::LspAdapter for GoLspAdapter {
             filter_range,
         })
     }
-    fn prepare_initialize_params(
-        &self,
-        mut original: InitializeParams,
-    ) -> Result<InitializeParams> {
-        #[allow(deprecated)]
-        let _ = original.root_uri.take();
-        Ok(original)
-    }
 }
 
 fn parse_version_output(output: &Output) -> Result<&str> {

crates/lsp/src/lsp.rs 🔗

@@ -329,6 +329,7 @@ impl lsp_types::notification::Notification for ServerStatus {
 
 impl LanguageServer {
     /// Starts a language server process.
+    #[allow(clippy::too_many_arguments)]
     pub fn new(
         stderr_capture: Arc<Mutex<Option<String>>>,
         server_id: LanguageServerId,
@@ -336,6 +337,7 @@ impl LanguageServer {
         binary: LanguageServerBinary,
         root_path: &Path,
         code_action_kinds: Option<Vec<CodeActionKind>>,
+        workspace_folders: Arc<Mutex<BTreeSet<Url>>>,
         cx: AsyncApp,
     ) -> Result<Self> {
         let working_dir = if root_path.is_dir() {
@@ -383,6 +385,7 @@ impl LanguageServer {
             code_action_kinds,
             binary,
             root_uri,
+            workspace_folders,
             cx,
             move |notification| {
                 log::info!(
@@ -409,6 +412,7 @@ impl LanguageServer {
         code_action_kinds: Option<Vec<CodeActionKind>>,
         binary: LanguageServerBinary,
         root_uri: Url,
+        workspace_folders: Arc<Mutex<BTreeSet<Url>>>,
         cx: AsyncApp,
         on_unhandled_notification: F,
     ) -> Self
@@ -491,7 +495,7 @@ impl LanguageServer {
             io_tasks: Mutex::new(Some((input_task, output_task))),
             output_done_rx: Mutex::new(Some(output_done_rx)),
             server: Arc::new(Mutex::new(server)),
-            workspace_folders: Default::default(),
+            workspace_folders,
             root_uri,
         }
     }
@@ -617,6 +621,16 @@ impl LanguageServer {
     }
 
     pub fn default_initialize_params(&self, cx: &App) -> InitializeParams {
+        let workspace_folders = self
+            .workspace_folders
+            .lock()
+            .iter()
+            .cloned()
+            .map(|uri| WorkspaceFolder {
+                name: Default::default(),
+                uri,
+            })
+            .collect::<Vec<_>>();
         #[allow(deprecated)]
         InitializeParams {
             process_id: None,
@@ -791,7 +805,7 @@ impl LanguageServer {
                 }),
             },
             trace: None,
-            workspace_folders: Some(vec![]),
+            workspace_folders: Some(workspace_folders),
             client_info: release_channel::ReleaseChannel::try_global(cx).map(|release_channel| {
                 ClientInfo {
                     name: release_channel.display_name().to_string(),
@@ -1260,24 +1274,27 @@ impl LanguageServer {
     }
     pub fn set_workspace_folders(&self, folders: BTreeSet<Url>) {
         let mut workspace_folders = self.workspace_folders.lock();
+
+        let old_workspace_folders = std::mem::take(&mut *workspace_folders);
         let added: Vec<_> = folders
-            .iter()
+            .difference(&old_workspace_folders)
             .map(|uri| WorkspaceFolder {
                 uri: uri.clone(),
                 name: String::default(),
             })
             .collect();
 
-        let removed: Vec<_> = std::mem::replace(&mut *workspace_folders, folders)
-            .into_iter()
+        let removed: Vec<_> = old_workspace_folders
+            .difference(&folders)
             .map(|uri| WorkspaceFolder {
                 uri: uri.clone(),
                 name: String::default(),
             })
             .collect();
         let should_notify = !added.is_empty() || !removed.is_empty();
-
         if should_notify {
+            *workspace_folders = folders;
+            drop(workspace_folders);
             let params = DidChangeWorkspaceFoldersParams {
                 event: WorkspaceFoldersChangeEvent { added, removed },
             };
@@ -1392,6 +1409,7 @@ impl FakeLanguageServer {
         let server_name = LanguageServerName(name.clone().into());
         let process_name = Arc::from(name.as_str());
         let root = Self::root_path();
+        let workspace_folders: Arc<Mutex<BTreeSet<Url>>> = Default::default();
         let mut server = LanguageServer::new_internal(
             server_id,
             server_name.clone(),
@@ -1403,6 +1421,7 @@ impl FakeLanguageServer {
             None,
             binary.clone(),
             root,
+            workspace_folders.clone(),
             cx.clone(),
             |_| {},
         );
@@ -1421,6 +1440,7 @@ impl FakeLanguageServer {
                     None,
                     binary,
                     Self::root_path(),
+                    workspace_folders,
                     cx.clone(),
                     move |msg| {
                         notifications_tx

crates/prettier/src/prettier.rs 🔗

@@ -279,6 +279,7 @@ impl Prettier {
             server_binary,
             &prettier_dir,
             None,
+            Default::default(),
             cx.clone(),
         )
         .context("prettier server creation")?;

crates/project/src/lsp_store.rs 🔗

@@ -206,14 +206,14 @@ impl LocalLspStore {
         );
 
         let binary = self.get_language_server_binary(adapter.clone(), delegate.clone(), true, cx);
-
+        let pending_workspace_folders: Arc<Mutex<BTreeSet<Url>>> = Default::default();
         let pending_server = cx.spawn({
             let adapter = adapter.clone();
             let server_name = adapter.name.clone();
             let stderr_capture = stderr_capture.clone();
             #[cfg(any(test, feature = "test-support"))]
             let lsp_store = self.weak.clone();
-
+            let pending_workspace_folders = pending_workspace_folders.clone();
             move |cx| async move {
                 let binary = binary.await?;
                 #[cfg(any(test, feature = "test-support"))]
@@ -239,12 +239,12 @@ impl LocalLspStore {
                     binary,
                     &root_path,
                     adapter.code_action_kinds(),
+                    pending_workspace_folders,
                     cx,
                 )
             }
         });
 
-        let pending_workspace_folders: Arc<Mutex<BTreeSet<Url>>> = Default::default();
         let startup = {
             let server_name = adapter.name.0.clone();
             let delegate = delegate as Arc<dyn LspAdapterDelegate>;
@@ -7551,10 +7551,11 @@ impl LspStore {
 
         // Update language_servers collection with Running variant of LanguageServerState
         // indicating that the server is up and running and ready
+        let workspace_folders = workspace_folders.lock().clone();
         local.language_servers.insert(
             server_id,
             LanguageServerState::running(
-                workspace_folders.lock().clone(),
+                workspace_folders,
                 adapter.clone(),
                 language_server.clone(),
                 None,