diff --git a/crates/oxc_language_server/src/main.rs b/crates/oxc_language_server/src/main.rs index e06c700abc643..9a2a66dd68339 100644 --- a/crates/oxc_language_server/src/main.rs +++ b/crates/oxc_language_server/src/main.rs @@ -88,13 +88,19 @@ impl LanguageServer for Backend { let settings = value.get_mut("settings")?.take(); serde_json::from_value::(settings).ok() }); + let capabilities = Capabilities::from(params.capabilities); // ToDo: add support for multiple workspace folders // maybe fallback when the client does not support it let root_worker = WorkspaceWorker::new(¶ms.root_uri.unwrap()); - // ToDo: only call init_linter when the client passed a valid Options struct - // if not and the client supports `workspace/configuration`, we should request them in `initialized` - root_worker.init_linter(&options.clone().unwrap_or_default()).await; + + // When the client did not send our custom `initialization_options`, + // or the client does not support `workspace/configuration` request, + // start the linter. We do not start the linter when the client support the request, + // we will init the linter after requesting for the workspace configuration. + if !capabilities.workspace_configuration || options.is_some() { + root_worker.init_linter(&options.clone().unwrap_or_default()).await; + } *self.workspace_workers.lock().await = vec![root_worker]; @@ -103,7 +109,6 @@ impl LanguageServer for Backend { info!("language server version: {server_version}"); } - let capabilities = Capabilities::from(params.capabilities); self.capabilities.set(capabilities.clone()).map_err(|err| { let message = match err { SetError::AlreadyInitializedError(_) => { @@ -125,6 +130,44 @@ impl LanguageServer for Backend { }) } + async fn initialized(&self, _params: InitializedParams) { + debug!("oxc initialized."); + + if !self.capabilities.get().unwrap().workspace_configuration { + // every worker should be initialized already in `initialize` request + return; + } + + let workers = &*self.workspace_workers.lock().await; + let needed_configurations = + ConcurrentHashMap::with_capacity_and_hasher(workers.len(), FxBuildHasher); + let needed_configurations = needed_configurations.pin_owned(); + for worker in workers { + if worker.needs_init_linter().await { + needed_configurations.insert(worker.get_root_uri().unwrap(), worker); + } + } + + let configurations = + self.request_workspace_configuration(needed_configurations.keys().collect()).await; + for (index, worker) in needed_configurations.values().enumerate() { + worker + .init_linter( + configurations + .get(index) + .unwrap_or(&None) + .as_ref() + .unwrap_or(&Options::default()), + ) + .await; + } + } + + async fn shutdown(&self) -> Result<()> { + self.clear_all_diagnostics().await; + Ok(()) + } + async fn did_change_configuration(&self, params: DidChangeConfigurationParams) { let workers = self.workspace_workers.lock().await; let new_diagnostics: papaya::HashMap, FxBuildHasher> = @@ -255,15 +298,6 @@ impl LanguageServer for Backend { self.publish_all_diagnostics(x).await; } - async fn initialized(&self, _params: InitializedParams) { - debug!("oxc initialized."); - } - - async fn shutdown(&self) -> Result<()> { - self.clear_all_diagnostics().await; - Ok(()) - } - async fn did_save(&self, params: DidSaveTextDocumentParams) { debug!("oxc server did save"); let uri = ¶ms.text_document.uri; @@ -396,6 +430,38 @@ impl LanguageServer for Backend { } impl Backend { + /// Request the workspace configuration from the client + /// and return the options for each workspace folder. + /// The check if the client support workspace configuration, should be done before. + async fn request_workspace_configuration(&self, uris: Vec<&Uri>) -> Vec> { + let length = uris.len(); + let config_items = uris + .into_iter() + .map(|uri| ConfigurationItem { + scope_uri: Some(uri.clone()), + section: Some("oxc_language_server".into()), + }) + .collect::>(); + + let Ok(configs) = self.client.configuration(config_items).await else { + debug!("failed to get configuration"); + // return none for each workspace folder + return vec![None; length]; + }; + + let mut options = vec![]; + for config in configs { + options.push(serde_json::from_value::(config).ok()); + } + + debug_assert!( + options.len() == length, + "the number of configuration items should be the same as the number of workspace folders" + ); + + options + } + // clears all diagnostics for workspace folders async fn clear_all_diagnostics(&self) { let mut cleared_diagnostics = vec![]; diff --git a/crates/oxc_language_server/src/worker.rs b/crates/oxc_language_server/src/worker.rs index 62641d5076913..c888f1fee4875 100644 --- a/crates/oxc_language_server/src/worker.rs +++ b/crates/oxc_language_server/src/worker.rs @@ -56,6 +56,10 @@ impl WorkspaceWorker { Some(ServerLinter::new(self.root_uri.get().unwrap(), options)); } + pub async fn needs_init_linter(&self) -> bool { + self.server_linter.read().await.is_none() + } + pub async fn remove_diagnostics(&self, uri: &Uri) { self.diagnostics_report_map.read().await.pin().remove(&uri.to_string()); } diff --git a/editors/vscode/client/extension.ts b/editors/vscode/client/extension.ts index 7ccda3a2cbcf4..d5d510c3298b9 100644 --- a/editors/vscode/client/extension.ts +++ b/editors/vscode/client/extension.ts @@ -12,7 +12,12 @@ import { workspace, } from 'vscode'; -import { ExecuteCommandRequest, MessageType, ShowMessageNotification } from 'vscode-languageclient'; +import { + ConfigurationParams, + ExecuteCommandRequest, + MessageType, + ShowMessageNotification, +} from 'vscode-languageclient'; import { Executable, LanguageClient, LanguageClientOptions, ServerOptions } from 'vscode-languageclient/node'; @@ -213,6 +218,19 @@ export async function activate(context: ExtensionContext) { }, outputChannel, traceOutputChannel: outputChannel, + middleware: { + workspace: { + configuration: (params: ConfigurationParams) => { + return params.items.map(item => { + if (item.section !== 'oxc_language_server') { + return null; + } + + return configService.rootServerConfig.toLanguageServerConfig() ?? null; + }); + }, + }, + }, }; // Create the language client and start the client.