From db05a1522b8a2ed7e109a49fa4eb23858f61636b Mon Sep 17 00:00:00 2001 From: Sysix <3897725+Sysix@users.noreply.github.com> Date: Mon, 21 Apr 2025 18:12:14 +0000 Subject: [PATCH] refactor(language_server): do not request for worspace configuration when the client does not support it (#10507) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently working on supporting multiple workspace folder. `workspace/didChangeConfiguratoin` does not specific the Workspace, so some clients expect to request them. We need to check if the client support this or fallback to a global configuration change (changing all workspaces). ChatGPT: > what if workspace/configuration is not supported by the clinet ChatGPT: Ah, good follow-up — if the client does not support workspace/configuration, things get more limited and require some careful handling. 🔧 First: Check If the Client Supports It When your language server receives the initialize request from the client, it includes its capabilities. Look for: ``` "capabilities": { "workspace": { "configuration": true } } ``` > If that configuration field is false or missing, then you cannot use workspace/configuration, and the client will only send global settings via workspace/didChangeConfiguration. --- .../oxc_language_server/src/capabilities.rs | 14 ++++++++-- crates/oxc_language_server/src/main.rs | 26 +++++++++++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/crates/oxc_language_server/src/capabilities.rs b/crates/oxc_language_server/src/capabilities.rs index 2caeda662e537..cb4fcd2ff6817 100644 --- a/crates/oxc_language_server/src/capabilities.rs +++ b/crates/oxc_language_server/src/capabilities.rs @@ -7,11 +7,12 @@ use tower_lsp_server::lsp_types::{ use crate::{code_actions::CODE_ACTION_KIND_SOURCE_FIX_ALL_OXC, commands::LSP_COMMANDS}; -#[derive(Clone)] +#[derive(Clone, Default)] pub struct Capabilities { pub code_action_provider: bool, pub workspace_apply_edit: bool, pub workspace_execute_command: bool, + pub workspace_configuration: bool, } impl From for Capabilities { @@ -28,8 +29,17 @@ impl From for Capabilities { value.workspace.as_ref().is_some_and(|workspace| workspace.apply_edit.is_some()); let workspace_execute_command = value.workspace.as_ref().is_some_and(|workspace| workspace.execute_command.is_some()); + let workspace_configuration = value + .workspace + .as_ref() + .is_some_and(|workspace| workspace.configuration.is_some_and(|config| config)); - Self { code_action_provider, workspace_apply_edit, workspace_execute_command } + Self { + code_action_provider, + workspace_apply_edit, + workspace_execute_command, + workspace_configuration, + } } } diff --git a/crates/oxc_language_server/src/main.rs b/crates/oxc_language_server/src/main.rs index 3d561342c5228..3dd87e934b73d 100644 --- a/crates/oxc_language_server/src/main.rs +++ b/crates/oxc_language_server/src/main.rs @@ -45,6 +45,7 @@ const OXC_CONFIG_FILE: &str = ".oxlintrc.json"; struct Backend { client: Client, root_uri: OnceCell>, + capabilities: OnceCell, server_linter: RwLock, diagnostics_report_map: ConcurrentHashMap>, options: Mutex, @@ -102,13 +103,25 @@ impl LanguageServer for Backend { *self.options.lock().await = value; } + let capabilities = Capabilities::from(params.capabilities); + self.capabilities.set(capabilities.clone()).map_err(|err| { + let message = match err { + SetError::AlreadyInitializedError(_) => { + "capabilities are already initialized".into() + } + SetError::InitializingError(_) => "initializing error".into(), + }; + + Error { code: ErrorCode::ParseError, message, data: None } + })?; + self.init_nested_configs().await; let oxlintrc = self.init_linter_config().await; self.init_ignore_glob(oxlintrc).await; Ok(InitializeResult { server_info: Some(ServerInfo { name: "oxc".into(), version: None }), offset_encoding: None, - capabilities: Capabilities::from(params.capabilities).into(), + capabilities: capabilities.into(), }) } @@ -116,7 +129,11 @@ impl LanguageServer for Backend { let changed_options = if let Ok(options) = serde_json::from_value::(params.settings) { options - } else { + } else if self + .capabilities + .get() + .is_some_and(|capabilities| capabilities.workspace_configuration) + { // Fallback if some client didn't took changed configuration in params of `workspace/configuration` let Some(options) = self .client @@ -133,6 +150,10 @@ impl LanguageServer for Backend { return; }; options + } else { + // the client did not give os changes to the configuration and he is not able to respond to `workspace/configuration` + // so we fallback to the current configuration + self.options.lock().await.clone() }; let current_option = &self.options.lock().await.clone(); @@ -565,6 +586,7 @@ async fn main() { let (service, socket) = LspService::build(|client| Backend { client, root_uri: OnceCell::new(), + capabilities: OnceCell::new(), server_linter: RwLock::new(server_linter), diagnostics_report_map, options: Mutex::new(Options::default()),