diff --git a/crates/extension/src/extension.rs b/crates/extension/src/extension.rs index e222d49718..95c2049194 100644 --- a/crates/extension/src/extension.rs +++ b/crates/extension/src/extension.rs @@ -76,6 +76,20 @@ pub trait Extension: Send + Sync + 'static { worktree: Arc, ) -> Result>; + async fn language_server_additional_initialization_options( + &self, + language_server_id: LanguageServerName, + target_language_server_id: LanguageServerName, + worktree: Arc, + ) -> Result>; + + async fn language_server_additional_workspace_configuration( + &self, + language_server_id: LanguageServerName, + target_language_server_id: LanguageServerName, + worktree: Arc, + ) -> Result>; + async fn labels_for_completions( &self, language_server_id: LanguageServerName, diff --git a/crates/extension_api/src/extension_api.rs b/crates/extension_api/src/extension_api.rs index f84c98af0c..ca0772e6f2 100644 --- a/crates/extension_api/src/extension_api.rs +++ b/crates/extension_api/src/extension_api.rs @@ -93,6 +93,26 @@ pub trait Extension: Send + Sync { Ok(None) } + /// Returns the initialization options to pass to the other language server. + fn language_server_additional_initialization_options( + &mut self, + _language_server_id: &LanguageServerId, + _target_language_server_id: &LanguageServerId, + _worktree: &Worktree, + ) -> Result> { + Ok(None) + } + + /// Returns the workspace configuration options to pass to the other language server. + fn language_server_additional_workspace_configuration( + &mut self, + _language_server_id: &LanguageServerId, + _target_language_server_id: &LanguageServerId, + _worktree: &Worktree, + ) -> Result> { + Ok(None) + } + /// Returns the label for the given completion. fn label_for_completion( &self, @@ -235,6 +255,38 @@ impl wit::Guest for Component { .and_then(|value| serde_json::to_string(&value).ok())) } + fn language_server_additional_initialization_options( + language_server_id: String, + target_language_server_id: String, + worktree: &Worktree, + ) -> Result, String> { + let language_server_id = LanguageServerId(language_server_id); + let target_language_server_id = LanguageServerId(target_language_server_id); + Ok(extension() + .language_server_additional_initialization_options( + &language_server_id, + &target_language_server_id, + worktree, + )? + .and_then(|value| serde_json::to_string(&value).ok())) + } + + fn language_server_additional_workspace_configuration( + language_server_id: String, + target_language_server_id: String, + worktree: &Worktree, + ) -> Result, String> { + let language_server_id = LanguageServerId(language_server_id); + let target_language_server_id = LanguageServerId(target_language_server_id); + Ok(extension() + .language_server_additional_workspace_configuration( + &language_server_id, + &target_language_server_id, + worktree, + )? + .and_then(|value| serde_json::to_string(&value).ok())) + } + fn labels_for_completions( language_server_id: String, completions: Vec, diff --git a/crates/extension_api/wit/since_v0.4.0/extension.wit b/crates/extension_api/wit/since_v0.4.0/extension.wit index 95aaec5469..3caf8b60b7 100644 --- a/crates/extension_api/wit/since_v0.4.0/extension.wit +++ b/crates/extension_api/wit/since_v0.4.0/extension.wit @@ -95,6 +95,12 @@ world extension { /// Returns the workspace configuration options to pass to the language server. export language-server-workspace-configuration: func(language-server-id: string, worktree: borrow) -> result, string>; + /// Returns the initialization options to pass to the other language server. + export language-server-additional-initialization-options: func(language-server-id: string, target-language-server-id: string, worktree: borrow) -> result, string>; + + /// Returns the workspace configuration options to pass to the other language server. + export language-server-additional-workspace-configuration: func(language-server-id: string, target-language-server-id: string, worktree: borrow) -> result, string>; + /// A label containing some code. record code-label { /// The source code to parse with Tree-sitter. diff --git a/crates/extension_host/src/wasm_host.rs b/crates/extension_host/src/wasm_host.rs index 0d9c2d053f..a01662f63f 100644 --- a/crates/extension_host/src/wasm_host.rs +++ b/crates/extension_host/src/wasm_host.rs @@ -140,6 +140,56 @@ impl extension::Extension for WasmExtension { .await } + async fn language_server_additional_initialization_options( + &self, + language_server_id: LanguageServerName, + target_language_server_id: LanguageServerName, + worktree: Arc, + ) -> Result> { + self.call(|extension, store| { + async move { + let resource = store.data_mut().table().push(worktree)?; + let options = extension + .call_language_server_additional_initialization_options( + store, + &language_server_id, + &target_language_server_id, + resource, + ) + .await? + .map_err(|err| anyhow!("{err}"))?; + anyhow::Ok(options) + } + .boxed() + }) + .await + } + + async fn language_server_additional_workspace_configuration( + &self, + language_server_id: LanguageServerName, + target_language_server_id: LanguageServerName, + worktree: Arc, + ) -> Result> { + self.call(|extension, store| { + async move { + let resource = store.data_mut().table().push(worktree)?; + let options = extension + .call_language_server_additional_workspace_configuration( + store, + &language_server_id, + &target_language_server_id, + resource, + ) + .await? + .map_err(|err| anyhow!("{err}"))?; + anyhow::Ok(options) + } + .boxed() + }) + .await + } + async fn labels_for_completions( &self, language_server_id: LanguageServerName, diff --git a/crates/extension_host/src/wasm_host/wit.rs b/crates/extension_host/src/wasm_host/wit.rs index 823e714d8a..6c07a181da 100644 --- a/crates/extension_host/src/wasm_host/wit.rs +++ b/crates/extension_host/src/wasm_host/wit.rs @@ -365,6 +365,58 @@ impl Extension { } } + pub async fn call_language_server_additional_initialization_options( + &self, + store: &mut Store, + language_server_id: &LanguageServerName, + target_language_server_id: &LanguageServerName, + resource: Resource>, + ) -> Result, String>> { + match self { + Extension::V040(ext) => { + ext.call_language_server_additional_initialization_options( + store, + &language_server_id.0, + &target_language_server_id.0, + resource, + ) + .await + } + Extension::V030(_) + | Extension::V020(_) + | Extension::V010(_) + | Extension::V006(_) + | Extension::V004(_) + | Extension::V001(_) => Ok(Ok(None)), + } + } + + pub async fn call_language_server_additional_workspace_configuration( + &self, + store: &mut Store, + language_server_id: &LanguageServerName, + target_language_server_id: &LanguageServerName, + resource: Resource>, + ) -> Result, String>> { + match self { + Extension::V040(ext) => { + ext.call_language_server_additional_workspace_configuration( + store, + &language_server_id.0, + &target_language_server_id.0, + resource, + ) + .await + } + Extension::V030(_) + | Extension::V020(_) + | Extension::V010(_) + | Extension::V006(_) + | Extension::V004(_) + | Extension::V001(_) => Ok(Ok(None)), + } + } + pub async fn call_labels_for_completions( &self, store: &mut Store, diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index bda5db426e..9938cf911e 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -305,6 +305,7 @@ pub trait LspAdapterDelegate: Send + Sync { fn worktree_root_path(&self) -> &Path; fn exists(&self, path: &Path, is_dir: Option) -> bool; fn update_status(&self, language: LanguageServerName, status: BinaryStatus); + fn registered_lsp_adapters(&self) -> Vec>; async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option>; async fn npm_package_installed_version( @@ -515,6 +516,26 @@ pub trait LspAdapter: 'static + Send + Sync { Ok(serde_json::json!({})) } + async fn additional_initialization_options( + self: Arc, + _target_language_server_id: LanguageServerName, + _: &dyn Fs, + _: &Arc, + ) -> Result> { + Ok(None) + } + + async fn additional_workspace_configuration( + self: Arc, + _target_language_server_id: LanguageServerName, + _: &dyn Fs, + _: &Arc, + _: Arc, + _cx: &mut AsyncApp, + ) -> Result> { + Ok(None) + } + /// Returns a list of code actions supported by a given LspAdapter fn code_action_kinds(&self) -> Option> { Some(vec![ diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index d4deca5944..f99c927c69 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -912,6 +912,15 @@ impl LanguageRegistry { .unwrap_or_default() } + pub fn all_lsp_adapters(&self) -> Vec> { + self.state + .read() + .all_lsp_adapters + .values() + .cloned() + .collect() + } + pub fn adapter_for_name(&self, name: &LanguageServerName) -> Option> { self.state.read().all_lsp_adapters.get(name).cloned() } diff --git a/crates/language_extension/src/extension_lsp_adapter.rs b/crates/language_extension/src/extension_lsp_adapter.rs index 3ccefb1f56..148e5b5902 100644 --- a/crates/language_extension/src/extension_lsp_adapter.rs +++ b/crates/language_extension/src/extension_lsp_adapter.rs @@ -267,6 +267,58 @@ impl LspAdapter for ExtensionLspAdapter { }) } + async fn additional_initialization_options( + self: Arc, + target_language_server_id: LanguageServerName, + _: &dyn Fs, + delegate: &Arc, + ) -> Result> { + let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _; + let json_options: Option = self + .extension + .language_server_additional_initialization_options( + self.language_server_id.clone(), + target_language_server_id.clone(), + delegate, + ) + .await?; + Ok(if let Some(json_options) = json_options { + serde_json::from_str(&json_options).with_context(|| { + format!( + "failed to parse additional_initialization_options from extension: {json_options}" + ) + })? + } else { + None + }) + } + + async fn additional_workspace_configuration( + self: Arc, + target_language_server_id: LanguageServerName, + _: &dyn Fs, + delegate: &Arc, + _: Arc, + _cx: &mut AsyncApp, + ) -> Result> { + let delegate = Arc::new(WorktreeDelegateAdapter(delegate.clone())) as _; + let json_options: Option = self + .extension + .language_server_additional_workspace_configuration( + self.language_server_id.clone(), + target_language_server_id.clone(), + delegate, + ) + .await?; + Ok(if let Some(json_options) = json_options { + serde_json::from_str(&json_options).with_context(|| { + format!("failed to parse additional_workspace_configuration from extension: {json_options}") + })? + } else { + None + }) + } + async fn labels_for_completions( self: Arc, completions: &[lsp::CompletionItem], diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index 3361ac1662..0f088aa46d 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -251,17 +251,21 @@ impl LocalLspStore { let toolchains = this.update(cx, |this, cx| this.toolchain_store(cx))?; let language_server = pending_server.await?; - let workspace_config = adapter - .adapter - .clone() - .workspace_configuration(fs.as_ref(), &delegate, toolchains.clone(), cx) - .await?; + let workspace_config = Self::workspace_configuration_for_adapter( + adapter.adapter.clone(), + fs.as_ref(), + &delegate, + toolchains.clone(), + cx, + ) + .await?; - let mut initialization_options = adapter - .adapter - .clone() - .initialization_options(fs.as_ref(), &(delegate)) - .await?; + let mut initialization_options = Self::initialization_options_for_adapter( + adapter.adapter.clone(), + fs.as_ref(), + &delegate, + ) + .await?; match (&mut initialization_options, override_options) { (Some(initialization_options), Some(override_options)) => { @@ -478,9 +482,16 @@ impl LocalLspStore { async move { let toolchains = this.update(&mut cx, |this, cx| this.toolchain_store(cx))?; - let workspace_config = adapter - .workspace_configuration(fs.as_ref(), &delegate, toolchains, &mut cx) - .await?; + + let workspace_config = Self::workspace_configuration_for_adapter( + adapter.clone(), + fs.as_ref(), + &delegate, + toolchains.clone(), + &mut cx, + ) + .await?; + Ok(params .items .into_iter() @@ -3225,6 +3236,67 @@ impl LocalLspStore { self.rebuild_watched_paths(language_server_id, cx); } + + async fn initialization_options_for_adapter( + adapter: Arc, + fs: &dyn Fs, + delegate: &Arc, + ) -> Result> { + let Some(mut initialization_config) = + adapter.clone().initialization_options(fs, delegate).await? + else { + return Ok(None); + }; + + for other_adapter in delegate.registered_lsp_adapters() { + if other_adapter.name() == adapter.name() { + continue; + } + if let Ok(Some(target_config)) = other_adapter + .clone() + .additional_initialization_options(adapter.name(), fs, delegate) + .await + { + merge_json_value_into(target_config.clone(), &mut initialization_config); + } + } + + Ok(Some(initialization_config)) + } + + async fn workspace_configuration_for_adapter( + adapter: Arc, + fs: &dyn Fs, + delegate: &Arc, + toolchains: Arc, + cx: &mut AsyncApp, + ) -> Result { + let mut workspace_config = adapter + .clone() + .workspace_configuration(fs, delegate, toolchains.clone(), cx) + .await?; + + for other_adapter in delegate.registered_lsp_adapters() { + if other_adapter.name() == adapter.name() { + continue; + } + if let Ok(Some(target_config)) = other_adapter + .clone() + .additional_workspace_configuration( + adapter.name(), + fs, + delegate, + toolchains.clone(), + cx, + ) + .await + { + merge_json_value_into(target_config.clone(), &mut workspace_config); + } + } + + Ok(workspace_config) + } } #[derive(Debug)] @@ -3764,8 +3836,8 @@ impl LspStore { for (adapter, server, delegate) in servers { adapter.clear_zed_json_schema_cache().await; - let Some(json_workspace_config) = adapter - .workspace_configuration( + let Some(json_workspace_config) = LocalLspStore::workspace_configuration_for_adapter( + adapter, fs.as_ref(), &delegate, toolchain_store.clone(), @@ -6065,10 +6137,15 @@ impl LspStore { let toolchain_store = this.update(cx, |this, cx| this.toolchain_store(cx)).ok()?; for (adapter, server, delegate) in servers { - let settings = adapter - .workspace_configuration(fs.as_ref(), &delegate, toolchain_store.clone(), cx) - .await - .ok()?; + let settings = LocalLspStore::workspace_configuration_for_adapter( + adapter, + fs.as_ref(), + &delegate, + toolchain_store.clone(), + cx, + ) + .await + .ok()?; server .notify::( @@ -9811,6 +9888,14 @@ impl LspAdapterDelegate for LocalLspAdapterDelegate { .update_lsp_status(server_name, status); } + fn registered_lsp_adapters(&self) -> Vec> { + self.language_registry + .all_lsp_adapters() + .into_iter() + .map(|adapter| adapter.adapter.clone() as Arc) + .collect() + } + async fn language_server_download_dir(&self, name: &LanguageServerName) -> Option> { let dir = self.language_registry.language_server_download_dir(name)?;