Ignore capability registrations with empty capabilities (#36000)

This commit is contained in:
Kirill Bulatov 2025-08-12 07:53:20 +03:00 committed by GitHub
parent b35e69692d
commit 481e3e5092
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -3367,20 +3367,6 @@ impl LocalLspStore {
} }
} }
fn parse_register_capabilities<T: serde::de::DeserializeOwned>(
reg: lsp::Registration,
) -> anyhow::Result<OneOf<bool, T>> {
let caps = match reg
.register_options
.map(|options| serde_json::from_value::<T>(options))
.transpose()?
{
None => OneOf::Left(true),
Some(options) => OneOf::Right(options),
};
Ok(caps)
}
fn notify_server_capabilities_updated(server: &LanguageServer, cx: &mut Context<LspStore>) { fn notify_server_capabilities_updated(server: &LanguageServer, cx: &mut Context<LspStore>) {
if let Some(capabilities) = serde_json::to_string(&server.capabilities()).ok() { if let Some(capabilities) = serde_json::to_string(&server.capabilities()).ok() {
cx.emit(LspStoreEvent::LanguageServerUpdate { cx.emit(LspStoreEvent::LanguageServerUpdate {
@ -11690,190 +11676,190 @@ impl LspStore {
// Ignore payload since we notify clients of setting changes unconditionally, relying on them pulling the latest settings. // Ignore payload since we notify clients of setting changes unconditionally, relying on them pulling the latest settings.
} }
"workspace/symbol" => { "workspace/symbol" => {
let options = parse_register_capabilities(reg)?; if let Some(options) = parse_register_capabilities(reg)? {
server.update_capabilities(|capabilities| { server.update_capabilities(|capabilities| {
capabilities.workspace_symbol_provider = Some(options); capabilities.workspace_symbol_provider = Some(options);
}); });
notify_server_capabilities_updated(&server, cx); notify_server_capabilities_updated(&server, cx);
}
} }
"workspace/fileOperations" => { "workspace/fileOperations" => {
let caps = reg if let Some(options) = reg.register_options {
.register_options let caps = serde_json::from_value(options)?;
.map(serde_json::from_value) server.update_capabilities(|capabilities| {
.transpose()? capabilities
.unwrap_or_default(); .workspace
server.update_capabilities(|capabilities| { .get_or_insert_default()
capabilities .file_operations = Some(caps);
.workspace });
.get_or_insert_default() notify_server_capabilities_updated(&server, cx);
.file_operations = Some(caps); }
});
notify_server_capabilities_updated(&server, cx);
} }
"workspace/executeCommand" => { "workspace/executeCommand" => {
let options = reg if let Some(options) = reg.register_options {
.register_options let options = serde_json::from_value(options)?;
.map(serde_json::from_value) server.update_capabilities(|capabilities| {
.transpose()? capabilities.execute_command_provider = Some(options);
.unwrap_or_default(); });
server.update_capabilities(|capabilities| { notify_server_capabilities_updated(&server, cx);
capabilities.execute_command_provider = Some(options); }
});
notify_server_capabilities_updated(&server, cx);
} }
"textDocument/rangeFormatting" => { "textDocument/rangeFormatting" => {
let options = parse_register_capabilities(reg)?; if let Some(options) = parse_register_capabilities(reg)? {
server.update_capabilities(|capabilities| { server.update_capabilities(|capabilities| {
capabilities.document_range_formatting_provider = Some(options); capabilities.document_range_formatting_provider = Some(options);
}); });
notify_server_capabilities_updated(&server, cx); notify_server_capabilities_updated(&server, cx);
}
} }
"textDocument/onTypeFormatting" => { "textDocument/onTypeFormatting" => {
let options = reg if let Some(options) = reg
.register_options .register_options
.map(serde_json::from_value) .map(serde_json::from_value)
.transpose()? .transpose()?
.unwrap_or_default(); {
server.update_capabilities(|capabilities| { server.update_capabilities(|capabilities| {
capabilities.document_on_type_formatting_provider = Some(options); capabilities.document_on_type_formatting_provider = Some(options);
}); });
notify_server_capabilities_updated(&server, cx); notify_server_capabilities_updated(&server, cx);
}
} }
"textDocument/formatting" => { "textDocument/formatting" => {
let options = parse_register_capabilities(reg)?; if let Some(options) = parse_register_capabilities(reg)? {
server.update_capabilities(|capabilities| { server.update_capabilities(|capabilities| {
capabilities.document_formatting_provider = Some(options); capabilities.document_formatting_provider = Some(options);
}); });
notify_server_capabilities_updated(&server, cx); notify_server_capabilities_updated(&server, cx);
}
} }
"textDocument/rename" => { "textDocument/rename" => {
let options = parse_register_capabilities(reg)?; if let Some(options) = parse_register_capabilities(reg)? {
server.update_capabilities(|capabilities| { server.update_capabilities(|capabilities| {
capabilities.rename_provider = Some(options); capabilities.rename_provider = Some(options);
}); });
notify_server_capabilities_updated(&server, cx); notify_server_capabilities_updated(&server, cx);
}
} }
"textDocument/inlayHint" => { "textDocument/inlayHint" => {
let options = parse_register_capabilities(reg)?; if let Some(options) = parse_register_capabilities(reg)? {
server.update_capabilities(|capabilities| { server.update_capabilities(|capabilities| {
capabilities.inlay_hint_provider = Some(options); capabilities.inlay_hint_provider = Some(options);
}); });
notify_server_capabilities_updated(&server, cx); notify_server_capabilities_updated(&server, cx);
}
} }
"textDocument/documentSymbol" => { "textDocument/documentSymbol" => {
let options = parse_register_capabilities(reg)?; if let Some(options) = parse_register_capabilities(reg)? {
server.update_capabilities(|capabilities| { server.update_capabilities(|capabilities| {
capabilities.document_symbol_provider = Some(options); capabilities.document_symbol_provider = Some(options);
}); });
notify_server_capabilities_updated(&server, cx); notify_server_capabilities_updated(&server, cx);
}
} }
"textDocument/codeAction" => { "textDocument/codeAction" => {
let options = reg if let Some(options) = reg
.register_options .register_options
.map(serde_json::from_value) .map(serde_json::from_value)
.transpose()?; .transpose()?
let provider_capability = match options { {
None => lsp::CodeActionProviderCapability::Simple(true), server.update_capabilities(|capabilities| {
Some(options) => lsp::CodeActionProviderCapability::Options(options), capabilities.code_action_provider =
}; Some(lsp::CodeActionProviderCapability::Options(options));
server.update_capabilities(|capabilities| { });
capabilities.code_action_provider = Some(provider_capability); notify_server_capabilities_updated(&server, cx);
}); }
notify_server_capabilities_updated(&server, cx);
} }
"textDocument/definition" => { "textDocument/definition" => {
let caps = parse_register_capabilities(reg)?; if let Some(options) = parse_register_capabilities(reg)? {
server.update_capabilities(|capabilities| { server.update_capabilities(|capabilities| {
capabilities.definition_provider = Some(caps); capabilities.definition_provider = Some(options);
}); });
notify_server_capabilities_updated(&server, cx); notify_server_capabilities_updated(&server, cx);
}
} }
"textDocument/completion" => { "textDocument/completion" => {
let caps = reg if let Some(caps) = reg
.register_options .register_options
.map(serde_json::from_value) .map(serde_json::from_value)
.transpose()? .transpose()?
.unwrap_or_default(); {
server.update_capabilities(|capabilities| { server.update_capabilities(|capabilities| {
capabilities.completion_provider = Some(caps); capabilities.completion_provider = Some(caps);
}); });
notify_server_capabilities_updated(&server, cx); notify_server_capabilities_updated(&server, cx);
}
} }
"textDocument/hover" => { "textDocument/hover" => {
let caps = reg if let Some(caps) = reg
.register_options .register_options
.map(serde_json::from_value) .map(serde_json::from_value)
.transpose()? .transpose()?
.unwrap_or_else(|| lsp::HoverProviderCapability::Simple(true)); {
server.update_capabilities(|capabilities| { server.update_capabilities(|capabilities| {
capabilities.hover_provider = Some(caps); capabilities.hover_provider = Some(caps);
}); });
notify_server_capabilities_updated(&server, cx); notify_server_capabilities_updated(&server, cx);
}
} }
"textDocument/signatureHelp" => { "textDocument/signatureHelp" => {
let caps = reg if let Some(caps) = reg
.register_options .register_options
.map(serde_json::from_value) .map(serde_json::from_value)
.transpose()? .transpose()?
.unwrap_or_default(); {
server.update_capabilities(|capabilities| { server.update_capabilities(|capabilities| {
capabilities.signature_help_provider = Some(caps); capabilities.signature_help_provider = Some(caps);
}); });
notify_server_capabilities_updated(&server, cx); notify_server_capabilities_updated(&server, cx);
}
} }
"textDocument/synchronization" => { "textDocument/synchronization" => {
let caps = reg if let Some(caps) = reg
.register_options .register_options
.map(serde_json::from_value) .map(serde_json::from_value)
.transpose()? .transpose()?
.unwrap_or_else(|| { {
lsp::TextDocumentSyncCapability::Options( server.update_capabilities(|capabilities| {
lsp::TextDocumentSyncOptions::default(), capabilities.text_document_sync = Some(caps);
)
}); });
server.update_capabilities(|capabilities| { notify_server_capabilities_updated(&server, cx);
capabilities.text_document_sync = Some(caps); }
});
notify_server_capabilities_updated(&server, cx);
} }
"textDocument/codeLens" => { "textDocument/codeLens" => {
let caps = reg if let Some(caps) = reg
.register_options .register_options
.map(serde_json::from_value) .map(serde_json::from_value)
.transpose()? .transpose()?
.unwrap_or_else(|| lsp::CodeLensOptions { {
resolve_provider: None, server.update_capabilities(|capabilities| {
capabilities.code_lens_provider = Some(caps);
}); });
server.update_capabilities(|capabilities| { notify_server_capabilities_updated(&server, cx);
capabilities.code_lens_provider = Some(caps); }
});
notify_server_capabilities_updated(&server, cx);
} }
"textDocument/diagnostic" => { "textDocument/diagnostic" => {
let caps = reg if let Some(caps) = reg
.register_options .register_options
.map(serde_json::from_value) .map(serde_json::from_value)
.transpose()? .transpose()?
.unwrap_or_else(|| { {
lsp::DiagnosticServerCapabilities::RegistrationOptions( server.update_capabilities(|capabilities| {
lsp::DiagnosticRegistrationOptions::default(), capabilities.diagnostic_provider = Some(caps);
)
}); });
server.update_capabilities(|capabilities| { notify_server_capabilities_updated(&server, cx);
capabilities.diagnostic_provider = Some(caps); }
});
notify_server_capabilities_updated(&server, cx);
} }
"textDocument/colorProvider" => { "textDocument/colorProvider" => {
let caps = reg if let Some(caps) = reg
.register_options .register_options
.map(serde_json::from_value) .map(serde_json::from_value)
.transpose()? .transpose()?
.unwrap_or_else(|| lsp::ColorProviderCapability::Simple(true)); {
server.update_capabilities(|capabilities| { server.update_capabilities(|capabilities| {
capabilities.color_provider = Some(caps); capabilities.color_provider = Some(caps);
}); });
notify_server_capabilities_updated(&server, cx); notify_server_capabilities_updated(&server, cx);
}
} }
_ => log::warn!("unhandled capability registration: {reg:?}"), _ => log::warn!("unhandled capability registration: {reg:?}"),
} }
@ -12016,6 +12002,18 @@ impl LspStore {
} }
} }
// Registration with empty capabilities should be ignored.
// https://github.com/microsoft/vscode-languageserver-node/blob/d90a87f9557a0df9142cfb33e251cfa6fe27d970/client/src/common/formatting.ts#L67-L70
fn parse_register_capabilities<T: serde::de::DeserializeOwned>(
reg: lsp::Registration,
) -> anyhow::Result<Option<OneOf<bool, T>>> {
Ok(reg
.register_options
.map(|options| serde_json::from_value::<T>(options))
.transpose()?
.map(OneOf::Right))
}
fn subscribe_to_binary_statuses( fn subscribe_to_binary_statuses(
languages: &Arc<LanguageRegistry>, languages: &Arc<LanguageRegistry>,
cx: &mut Context<'_, LspStore>, cx: &mut Context<'_, LspStore>,