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 <noreply@anthropic.com>
This commit is contained in:
Shuhei KadowakiandClaude Opus 4.5 authored and GitHub committed 2026-02-04 23:45:52 +01:00
1 parent 4b2dcf1c68
commit 70f302cffa
3 files changed
+97 -12

No files matched your search

@@ -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
+23
View File
@@ -354,6 +354,17 @@ impl CachedLspAdapter {
.await
}
pub async fn settings_schema(
&self,
delegate: &Arc<dyn LspAdapterDelegate>,
cx: &mut AsyncApp,
) -> Option<serde_json::Value> {
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<Self>,
_delegate: &Arc<dyn LspAdapterDelegate>,
_cached_binary: OwnedMutexGuard<Option<(bool, LanguageServerBinary)>>,
_cx: &mut AsyncApp,
) -> Option<serde_json::Value> {
None
}
async fn workspace_configuration(
self: Arc<Self>,
_: &Arc<dyn LspAdapterDelegate>,
+41 -3
View File
@@ -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]