From 6a71a60a127e0abf2a5c72a0bfb8f84cf2d0d460 Mon Sep 17 00:00:00 2001 From: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> Date: Thu, 5 Feb 2026 07:45:52 +0900 Subject: [PATCH] lsp: Add schema support for LSP settings field (#48332) This extends the LSP settings schema system to also provide autocomplete for the `settings` field (used for `workspace/configuration` responses), in addition to the existing `initialization_options` support (#). Changes: - Add `settings_schema` method to `LspAdapter` trait and `CachedLspAdapter` - Update schema URL paths to be more explicit: - `lsp/{adapter}/initialization_options` for init options schema - `lsp/{adapter}/settings` for settings schema - Add schema resolution logic for the new settings path - Update tests to verify both schema references Release Notes: - Added autocomplete support for the `settings` field in LSP configuration, complementing the existing `initialization_options` autocomplete. Co-authored-by: Claude Opus 4.5 --- .../src/json_schema_store.rs | 42 ++++++++++++++---- crates/language/src/language.rs | 23 ++++++++++ crates/settings/src/settings_store.rs | 44 +++++++++++++++++-- 3 files changed, 97 insertions(+), 12 deletions(-) diff --git a/crates/json_schema_store/src/json_schema_store.rs b/crates/json_schema_store/src/json_schema_store.rs index 99db65377926898e15dec2b3cf7df060f4c068bd..299fe7b994e093b356e1024f5d2f5de003324cb0 100644 --- a/crates/json_schema_store/src/json_schema_store.rs +++ b/crates/json_schema_store/src/json_schema_store.rs @@ -209,7 +209,7 @@ async fn resolve_dynamic_schema( let schema = match schema_name { "settings" if rest.is_some_and(|r| r.starts_with("lsp/")) => { - let lsp_name = rest + let lsp_path = rest .and_then(|r| { r.strip_prefix( LSP_SETTINGS_SCHEMA_URL_PREFIX @@ -220,6 +220,26 @@ async fn resolve_dynamic_schema( }) .context("Invalid LSP schema path")?; + // Parse the schema type from the path: + // - "rust-analyzer/initialization_options" → initialization_options_schema + // - "rust-analyzer/settings" → settings_schema + enum LspSchemaKind { + InitializationOptions, + Settings, + } + let (lsp_name, schema_kind) = if let Some(adapter_name) = + lsp_path.strip_suffix("/initialization_options") + { + (adapter_name, LspSchemaKind::InitializationOptions) + } else if let Some(adapter_name) = lsp_path.strip_suffix("/settings") { + (adapter_name, LspSchemaKind::Settings) + } else { + anyhow::bail!( + "Invalid LSP schema path: expected '{{adapter}}/initialization_options' or '{{adapter}}/settings', got '{}'", + lsp_path + ); + }; + let adapter = languages .all_lsp_adapters() .into_iter() @@ -246,15 +266,19 @@ async fn resolve_dynamic_schema( "either LSP store is not in local mode or no worktree is available" ))?; - adapter - .initialization_options_schema(&delegate, cx) - .await - .unwrap_or_else(|| { - serde_json::json!({ - "type": "object", - "additionalProperties": true - }) + let schema = match schema_kind { + LspSchemaKind::InitializationOptions => { + adapter.initialization_options_schema(&delegate, cx).await + } + LspSchemaKind::Settings => adapter.settings_schema(&delegate, cx).await, + }; + + schema.unwrap_or_else(|| { + serde_json::json!({ + "type": "object", + "additionalProperties": true }) + }) } "settings" => { let lsp_adapter_names = languages diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 0ae17bdc3093db1254e4969f8431e2c7032a07ef..63fbc28f3cca111bfa1cfc1783b1a52e703caeb1 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -354,6 +354,17 @@ impl CachedLspAdapter { .await } + pub async fn settings_schema( + &self, + delegate: &Arc, + cx: &mut AsyncApp, + ) -> Option { + self.adapter + .clone() + .settings_schema(delegate, self.cached_binary.clone().lock_owned().await, cx) + .await + } + pub fn process_prompt_response(&self, context: &PromptResponseContext, cx: &mut AsyncApp) { self.adapter.process_prompt_response(context, cx) } @@ -493,6 +504,18 @@ pub trait LspAdapter: 'static + Send + Sync + DynLspInstaller { None } + /// Returns the JSON schema of the settings for the language server. + /// This corresponds to the `settings` field in `LspSettings`, which is used + /// to respond to `workspace/configuration` requests from the language server. + async fn settings_schema( + self: Arc, + _delegate: &Arc, + _cached_binary: OwnedMutexGuard>, + _cx: &mut AsyncApp, + ) -> Option { + None + } + async fn workspace_configuration( self: Arc, _: &Arc, diff --git a/crates/settings/src/settings_store.rs b/crates/settings/src/settings_store.rs index fdf2c93fda2ce3f192d12d09812d725c03ce6356..657d67fb64f33834c03125b67ea527997aaa5510 100644 --- a/crates/settings/src/settings_store.rs +++ b/crates/settings/src/settings_store.rs @@ -1077,7 +1077,13 @@ impl SettingsStore { properties_object.insert( "initialization_options".to_string(), serde_json::json!({ - "$ref": format!("{LSP_SETTINGS_SCHEMA_URL_PREFIX}{adapter_name}") + "$ref": format!("{LSP_SETTINGS_SCHEMA_URL_PREFIX}{adapter_name}/initialization_options") + }), + ); + properties_object.insert( + "settings".to_string(), + serde_json::json!({ + "$ref": format!("{LSP_SETTINGS_SCHEMA_URL_PREFIX}{adapter_name}/settings") }), ); } @@ -2397,7 +2403,23 @@ mod tests { .as_str() .unwrap(); - assert_eq!(init_options_ref, "zed://schemas/settings/lsp/rust-analyzer"); + assert_eq!( + init_options_ref, + "zed://schemas/settings/lsp/rust-analyzer/initialization_options" + ); + + let settings_ref = properties + .get("rust-analyzer") + .unwrap() + .pointer("/properties/settings/$ref") + .expect("settings should have a $ref") + .as_str() + .unwrap(); + + assert_eq!( + settings_ref, + "zed://schemas/settings/lsp/rust-analyzer/settings" + ); } #[gpui::test] @@ -2432,7 +2454,23 @@ mod tests { .as_str() .unwrap(); - assert_eq!(init_options_ref, "zed://schemas/settings/lsp/rust-analyzer"); + assert_eq!( + init_options_ref, + "zed://schemas/settings/lsp/rust-analyzer/initialization_options" + ); + + let settings_ref = properties + .get("rust-analyzer") + .unwrap() + .pointer("/properties/settings/$ref") + .expect("settings should have a $ref") + .as_str() + .unwrap(); + + assert_eq!( + settings_ref, + "zed://schemas/settings/lsp/rust-analyzer/settings" + ); } #[gpui::test]