diff --git a/apps/oxfmt/src/lsp/mod.rs b/apps/oxfmt/src/lsp/mod.rs index f00fc719644e8..14374c557a48c 100644 --- a/apps/oxfmt/src/lsp/mod.rs +++ b/apps/oxfmt/src/lsp/mod.rs @@ -1,4 +1,7 @@ -use std::path::{Path, PathBuf}; +use std::{ + path::{Path, PathBuf}, + sync::Arc, +}; use oxc_language_server::{LanguageId, run_server}; use tower_lsp_server::ls_types::Uri; @@ -51,10 +54,10 @@ pub async fn run_lsp(js_config_loader: JsConfigLoaderCb, external_formatter: Ext run_server( "oxfmt".to_string(), env!("CARGO_PKG_VERSION").to_string(), - vec![Box::new(server_formatter::ServerFormatterBuilder::new( + Arc::new(server_formatter::ServerFormatterBuilder::new( js_config_loader, external_formatter, - ))], + )), ) .await; } diff --git a/apps/oxlint/src/lsp/mod.rs b/apps/oxlint/src/lsp/mod.rs index 2a0140ef43b06..61e55a84519b3 100644 --- a/apps/oxlint/src/lsp/mod.rs +++ b/apps/oxlint/src/lsp/mod.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use oxc_linter::ExternalLinter; #[cfg(feature = "napi")] @@ -21,11 +23,11 @@ pub async fn run_lsp( oxc_language_server::run_server( "oxlint".to_string(), env!("CARGO_PKG_VERSION").to_string(), - vec![Box::new(crate::lsp::server_linter::ServerLinterBuilder::new( + Arc::new(crate::lsp::server_linter::ServerLinterBuilder::new( external_linter, #[cfg(feature = "napi")] js_config_loader, - ))], + )), ) .await; } diff --git a/crates/oxc_language_server/src/backend.rs b/crates/oxc_language_server/src/backend.rs index cb617adeb224b..4e846f204ed83 100644 --- a/crates/oxc_language_server/src/backend.rs +++ b/crates/oxc_language_server/src/backend.rs @@ -106,9 +106,9 @@ impl LanguageServer for Backend { let mut capabilities = Capabilities::from(params.capabilities); let mut server_capabilities = server_capabilities(); - for tool_builder in self.worker_manager.read_tool_builders() { - tool_builder.server_capabilities(&mut server_capabilities, &mut capabilities); - } + self.worker_manager + .read_tool_builder() + .server_capabilities(&mut server_capabilities, &mut capabilities); info!("initialize: {options:?}"); info!( @@ -906,11 +906,11 @@ impl Backend { /// The Backend will manage multiple [WorkspaceWorker]s and their configurations. /// It also holds the capabilities of the language server and an in-memory file system. /// The client is used to communicate with the LSP client. - pub fn new(client: Client, server_info: ServerInfo, tools: Vec>) -> Self { + pub fn new(client: Client, server_info: ServerInfo, tool: Arc) -> Self { Self { client, server_info, - worker_manager: WorkerManager::new(Arc::from(tools)), + worker_manager: WorkerManager::new(tool), capabilities: OnceCell::new(), file_system: Arc::new(RwLock::new(LSPFileSystem::default())), } diff --git a/crates/oxc_language_server/src/lib.rs b/crates/oxc_language_server/src/lib.rs index 219e45d340e25..4508e63f68026 100644 --- a/crates/oxc_language_server/src/lib.rs +++ b/crates/oxc_language_server/src/lib.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use rustc_hash::FxBuildHasher; use tower_lsp_server::ls_types::Uri; use tower_lsp_server::{LspService, Server, ls_types::ServerInfo}; @@ -32,11 +34,7 @@ impl<'a> TextDocument<'a> { } /// Run the language server -pub async fn run_server( - server_name: String, - server_version: String, - tools: Vec>, -) { +pub async fn run_server(server_name: String, server_version: String, tool: Arc) { let stdin = tokio::io::stdin(); let stdout = tokio::io::stdout(); @@ -44,7 +42,7 @@ pub async fn run_server( crate::backend::Backend::new( client, ServerInfo { name: server_name, version: Some(server_version) }, - tools, + tool, ) }) .finish(); diff --git a/crates/oxc_language_server/src/tests.rs b/crates/oxc_language_server/src/tests.rs index 526a6136236d2..0958f1552dadc 100644 --- a/crates/oxc_language_server/src/tests.rs +++ b/crates/oxc_language_server/src/tests.rs @@ -568,6 +568,8 @@ fn diagnostic(id: i64, uri: &str) -> Request { #[cfg(test)] mod test_suite { + use std::sync::Arc; + use serde_json::{Value, json}; use tower_lsp_server::{ jsonrpc::{Error, ErrorCode, Id, Response}, @@ -597,7 +599,9 @@ mod test_suite { #[tokio::test] async fn test_basic_start_and_shutdown_flow() { - let mut server = TestServer::new(|client| Backend::new(client, server_info(), vec![])); + let mut server = TestServer::new(|client| { + Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())) + }); // initialize request server.send_request(initialize_request(InitializeRequestOptions::default())).await; let initialize_result = server.recv_response().await; @@ -640,7 +644,7 @@ mod test_suite { }; let mut server = TestServer::new_initialized( - |client| Backend::new(client, server_info(), vec![]), + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(init_options), ) .await; @@ -678,7 +682,7 @@ mod test_suite { }; let mut server = TestServer::new_initialized( - |client| Backend::new(client, server_info(), vec![]), + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request_workspace_folders(init_options), ) .await; @@ -710,7 +714,9 @@ mod test_suite { ..Default::default() }; - let mut server = TestServer::new(|client| Backend::new(client, server_info(), vec![])); + let mut server = TestServer::new(|client| { + Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())) + }); let initialize = initialize_request_workspace_folders(init_options); let initialize_id = initialize.id().cloned(); @@ -736,7 +742,7 @@ mod test_suite { InitializeRequestOptions { workspace_configuration: true, ..Default::default() }; let mut server = TestServer::new_initialized( - |client| Backend::new(client, server_info(), vec![]), + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(init_options), ) .await; @@ -777,9 +783,7 @@ mod test_suite { InitializeRequestOptions { dynamic_watchers: true, ..Default::default() }; let mut server = TestServer::new_initialized( - |client| { - Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder::default())]) - }, + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(init_options), ) .await; @@ -829,9 +833,7 @@ mod test_suite { let init_options = InitializeRequestOptions { workspace_edit: true, ..Default::default() }; let mut server = TestServer::new_initialized( - |client| { - Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder::default())]) - }, + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(init_options), ) .await; @@ -907,7 +909,7 @@ mod test_suite { }; let mut server = TestServer::new_initialized( - |client| Backend::new(client, server_info(), vec![]), + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request_workspace_folders(init_options), ) .await; @@ -940,9 +942,7 @@ mod test_suite { #[tokio::test] async fn test_execute_workspace_command_with_no_edit() { let mut server = TestServer::new_initialized( - |client| { - Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder::default())]) - }, + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(InitializeRequestOptions::default()), ) .await; @@ -964,9 +964,7 @@ mod test_suite { #[tokio::test] async fn test_execute_workspace_command_with_invalid_command() { let mut server = TestServer::new_initialized( - |client| { - Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder::default())]) - }, + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(InitializeRequestOptions::default()), ) .await; @@ -996,9 +994,7 @@ mod test_suite { ); let mut server = TestServer::new_initialized( - |client| { - Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder::default())]) - }, + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(InitializeRequestOptions::default()), ) .await; @@ -1023,9 +1019,7 @@ mod test_suite { InitializeRequestOptions { dynamic_watchers: true, ..Default::default() }; let mut server = TestServer::new_initialized( - |client| { - Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder::default())]) - }, + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(init_options), ) .await; @@ -1052,9 +1046,7 @@ mod test_suite { InitializeRequestOptions { workspace_configuration: true, ..Default::default() }; let mut server = TestServer::new_initialized( - |client| { - Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder::default())]) - }, + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(init_options), ) .await; @@ -1081,9 +1073,7 @@ mod test_suite { ); let mut server = TestServer::new_initialized( - |client| { - Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder::default())]) - }, + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(InitializeRequestOptions::default()), ) .await; @@ -1108,9 +1098,7 @@ mod test_suite { InitializeRequestOptions { dynamic_watchers: true, ..Default::default() }; let mut server = TestServer::new_initialized( - |client| { - Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder::default())]) - }, + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(init_options), ) .await; @@ -1129,9 +1117,7 @@ mod test_suite { InitializeRequestOptions { dynamic_watchers: true, ..Default::default() }; let mut server = TestServer::new_initialized( - |client| { - Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder::default())]) - }, + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(init_options), ) .await; @@ -1150,9 +1136,7 @@ mod test_suite { let init_options = InitializeRequestOptions { dynamic_watchers: true, ..Default::default() }; let mut server = TestServer::new_initialized( - |client| { - Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder::default())]) - }, + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(init_options), ) .await; @@ -1180,7 +1164,7 @@ mod test_suite { Backend::new( client, server_info(), - vec![Box::new(FakeToolBuilder::new(DiagnosticMode::Push))], + Arc::new(FakeToolBuilder::new(DiagnosticMode::Push)), ) }, initialize_request(init_options), @@ -1227,7 +1211,7 @@ mod test_suite { Backend::new( client, server_info(), - vec![Box::new(FakeToolBuilder::new(DiagnosticMode::Pull))], + Arc::new(FakeToolBuilder::new(DiagnosticMode::Pull)), ) }, initialize_request(init_options), @@ -1256,9 +1240,7 @@ mod test_suite { InitializeRequestOptions { dynamic_watchers: true, ..Default::default() }; let mut server = TestServer::new_initialized( - |client| { - Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder::default())]) - }, + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(init_options), ) .await; @@ -1279,9 +1261,7 @@ mod test_suite { InitializeRequestOptions { dynamic_watchers: true, ..Default::default() }; let mut server = TestServer::new_initialized( - |client| { - Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder::default())]) - }, + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(init_options), ) .await; @@ -1313,9 +1293,7 @@ mod test_suite { }; let mut server = TestServer::new_initialized( - |client| { - Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder::default())]) - }, + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(init_options), ) .await; @@ -1344,7 +1322,7 @@ mod test_suite { Backend::new( client, server_info(), - vec![Box::new(FakeToolBuilder::new(DiagnosticMode::None))], + Arc::new(FakeToolBuilder::new(DiagnosticMode::None)), ) }, initialize_request(InitializeRequestOptions::default()), @@ -1370,7 +1348,7 @@ mod test_suite { Backend::new( client, server_info(), - vec![Box::new(FakeToolBuilder::new(DiagnosticMode::Push))], + Arc::new(FakeToolBuilder::new(DiagnosticMode::Push)), ) }, initialize_request(InitializeRequestOptions::default()), @@ -1415,7 +1393,7 @@ mod test_suite { Backend::new( client, server_info(), - vec![Box::new(FakeToolBuilder::new(DiagnosticMode::Pull))], + Arc::new(FakeToolBuilder::new(DiagnosticMode::Pull)), ) }, initialize_request(init_options), @@ -1443,9 +1421,7 @@ mod test_suite { #[tokio::test] async fn test_file_notifications() { let mut server = TestServer::new_initialized( - |client| { - Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder::default())]) - }, + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(InitializeRequestOptions::default()), ) .await; @@ -1462,9 +1438,7 @@ mod test_suite { #[tokio::test] async fn test_code_action_no_actions() { let mut server = TestServer::new_initialized( - |client| { - Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder::default())]) - }, + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(InitializeRequestOptions::default()), ) .await; @@ -1486,9 +1460,7 @@ mod test_suite { #[tokio::test] async fn test_code_actions_with_actions() { let mut server = TestServer::new_initialized( - |client| { - Backend::new(client, server_info(), vec![Box::new(FakeToolBuilder::default())]) - }, + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(InitializeRequestOptions::default()), ) .await; @@ -1517,7 +1489,7 @@ mod test_suite { Backend::new( client, server_info(), - vec![Box::new(FakeToolBuilder::new(DiagnosticMode::Push))], + Arc::new(FakeToolBuilder::new(DiagnosticMode::Push)), ) }, initialize_request(InitializeRequestOptions::default()), @@ -1551,7 +1523,7 @@ mod test_suite { Backend::new( client, server_info(), - vec![Box::new(FakeToolBuilder::new(DiagnosticMode::None))], + Arc::new(FakeToolBuilder::new(DiagnosticMode::None)), ) }, initialize_request(InitializeRequestOptions::default()), @@ -1572,7 +1544,7 @@ mod test_suite { Backend::new( client, server_info(), - vec![Box::new(FakeToolBuilder::new(DiagnosticMode::Push))], + Arc::new(FakeToolBuilder::new(DiagnosticMode::Push)), ) }, initialize_request(InitializeRequestOptions::default()), @@ -1608,7 +1580,7 @@ mod test_suite { Backend::new( client, server_info(), - vec![Box::new(FakeToolBuilder::new(DiagnosticMode::Push))], + Arc::new(FakeToolBuilder::new(DiagnosticMode::Push)), ) }, initialize_request(InitializeRequestOptions::default()), @@ -1651,7 +1623,7 @@ mod test_suite { Backend::new( client, server_info(), - vec![Box::new(FakeToolBuilder::new(DiagnosticMode::Pull))], + Arc::new(FakeToolBuilder::new(DiagnosticMode::Pull)), ) }, initialize_request(init_options), @@ -1675,7 +1647,7 @@ mod test_suite { Backend::new( client, server_info(), - vec![Box::new(FakeToolBuilder::new(DiagnosticMode::Pull))], + Arc::new(FakeToolBuilder::new(DiagnosticMode::Pull)), ) }, initialize_request(init_options), @@ -1720,7 +1692,7 @@ mod test_suite { #[tokio::test] async fn test_entering_single_file_mode_when_all_workspaces_removed() { let mut server = TestServer::new_initialized( - |client| Backend::new(client, server_info(), vec![]), + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request(InitializeRequestOptions::default()), ) .await; @@ -1762,7 +1734,7 @@ mod test_suite { #[tokio::test] async fn test_exiting_single_file_mode_when_workspace_added() { let mut server = TestServer::new_initialized( - |client| Backend::new(client, server_info(), vec![]), + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request_workspace_folders(single_file_mode_initialize()), ) .await; @@ -1801,7 +1773,7 @@ mod test_suite { #[tokio::test] async fn test_single_file_mode_creates_worker_on_open() { let mut server = TestServer::new_initialized( - |client| Backend::new(client, server_info(), vec![]), + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request_workspace_folders(single_file_mode_initialize()), ) .await; @@ -1830,7 +1802,7 @@ mod test_suite { #[tokio::test] async fn test_single_file_mode_removes_worker_on_last_close() { let mut server = TestServer::new_initialized( - |client| Backend::new(client, server_info(), vec![]), + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request_workspace_folders(single_file_mode_initialize()), ) .await; @@ -1858,7 +1830,7 @@ mod test_suite { #[tokio::test] async fn test_single_file_mode_keeps_worker_when_sibling_still_open() { let mut server = TestServer::new_initialized( - |client| Backend::new(client, server_info(), vec![]), + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request_workspace_folders(single_file_mode_initialize()), ) .await; @@ -1889,7 +1861,7 @@ mod test_suite { #[tokio::test] async fn test_single_file_mode_separate_workers_for_different_dirs() { let mut server = TestServer::new_initialized( - |client| Backend::new(client, server_info(), vec![]), + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request_workspace_folders(single_file_mode_initialize()), ) .await; @@ -1912,7 +1884,7 @@ mod test_suite { #[tokio::test] async fn test_single_file_mode_non_file_uri_skipped() { let mut server = TestServer::new_initialized( - |client| Backend::new(client, server_info(), vec![]), + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request_workspace_folders(single_file_mode_initialize()), ) .await; @@ -1935,7 +1907,7 @@ mod test_suite { Backend::new( client, server_info(), - vec![Box::new(FakeToolBuilder::new(DiagnosticMode::Push))], + Arc::new(FakeToolBuilder::new(DiagnosticMode::Push)), ) }, initialize_request_workspace_folders(single_file_mode_initialize()), @@ -1968,7 +1940,7 @@ mod test_suite { #[tokio::test] async fn test_single_file_mode_close_all_removes_all_workers() { let mut server = TestServer::new_initialized( - |client| Backend::new(client, server_info(), vec![]), + |client| Backend::new(client, server_info(), Arc::new(FakeToolBuilder::default())), initialize_request_workspace_folders(single_file_mode_initialize()), ) .await; diff --git a/crates/oxc_language_server/src/tool.rs b/crates/oxc_language_server/src/tool.rs index afdfd1bafba32..c5466b21001c6 100644 --- a/crates/oxc_language_server/src/tool.rs +++ b/crates/oxc_language_server/src/tool.rs @@ -58,6 +58,8 @@ pub trait Tool: Send + Sync { ) -> ToolRestartChanges; /// Check if this tool is responsible for handling the given command. + /// TODO: this is not needed anymore, we have only one tool per server, + /// we can remove this method and directly call execute_command on the tool. fn is_responsible_for_command(&self, _command: &str) -> bool { false } diff --git a/crates/oxc_language_server/src/worker.rs b/crates/oxc_language_server/src/worker.rs index ad7710e422bec..7ddb02561f784 100644 --- a/crates/oxc_language_server/src/worker.rs +++ b/crates/oxc_language_server/src/worker.rs @@ -1,6 +1,7 @@ +use std::sync::Arc; + use rustc_hash::{FxHashMap, FxHashSet}; use serde_json::json; -use std::sync::Arc; use tokio::sync::{Mutex, RwLock}; use tower_lsp_server::{ jsonrpc::ErrorCode, @@ -20,15 +21,15 @@ use crate::{ tool::{DiagnosticResult, Tool, ToolBuilder}, }; -/// A worker that manages the individual tools for a specific workspace +/// A worker that manages the individual tool for a specific workspace /// and reports back the results to the [`Backend`](crate::backend::Backend). /// -/// Each worker is responsible for a specific root URI and configures the tools `cwd` to that root URI. +/// Each worker is responsible for a specific root URI and configures the tool's `cwd` to that root URI. /// The [`Backend`](crate::backend::Backend) is responsible to target the correct worker for a given file URI. pub struct WorkspaceWorker { root_uri: Uri, - tools: RwLock>>, - builders: Arc<[Box]>, + tool: RwLock>>, + builder: Arc, // Initialized options from the client // If None, the worker has not been initialized yet pub(crate) options: Mutex>, @@ -45,13 +46,13 @@ impl WorkspaceWorker { /// Depending on the client, we need to request the workspace configuration in `initialized` request. pub fn new( root_uri: Uri, - builders: Arc<[Box]>, + builder: Arc, diagnostic_mode: DiagnosticMode, ) -> Self { Self { root_uri, - tools: RwLock::new(vec![]), - builders, + tool: RwLock::new(None), + builder, options: Mutex::new(None), diagnostic_mode, published_diagnostics: Mutex::new(FxHashSet::default()), @@ -66,11 +67,7 @@ impl WorkspaceWorker { /// Start all programs (linter, formatter) for the worker. /// This should be called after the client has sent the workspace configuration. pub async fn start_worker(&self, options: serde_json::Value) { - *self.tools.write().await = self - .builders - .iter() - .map(|builder| builder.build_boxed(&self.root_uri, options.clone())) - .collect(); + *self.tool.write().await = Some(self.builder.build_boxed(&self.root_uri, options.clone())); *self.options.lock().await = Some(options); } @@ -82,19 +79,17 @@ impl WorkspaceWorker { // clone the options to avoid locking the mutex let options_json = { self.options.lock().await.clone().unwrap_or_default() }; - self.tools - .read() - .await - .iter() - .filter_map(|tool| { - let patterns = tool.get_watcher_patterns(options_json.clone()); - if patterns.is_empty() { - None - } else { - Some(registration_tool_watcher_id(tool.name(), &self.root_uri, patterns)) - } - }) - .collect() + let tool_guard = self.tool.read().await; + let Some(tool) = tool_guard.as_ref() else { + return Vec::new(); + }; + + let patterns = tool.get_watcher_patterns(options_json.clone()); + if patterns.is_empty() { + Vec::new() + } else { + vec![registration_tool_watcher_id(tool.name(), &self.root_uri, patterns)] + } } /// Check if the worker needs to be initialized with options @@ -104,9 +99,9 @@ impl WorkspaceWorker { /// Remove all internal cache for the given URI, if any. pub async fn remove_uri_cache(&self, uri: &Uri) { - self.tools.read().await.iter().for_each(|tool| { + if let Some(tool) = self.tool.read().await.as_ref() { tool.remove_uri_cache(uri); - }); + } } /// Common aggregator for tool-provided diagnostics. @@ -120,19 +115,24 @@ impl WorkspaceWorker { { let mut aggregated: FxHashMap> = FxHashMap::default(); - for tool in self.tools.read().await.iter() { - let tool_diagnostics = run(tool, document); + let tool_diagnostics = { + let tool_guard = self.tool.read().await; + let Some(tool) = tool_guard.as_ref() else { + return Ok(Vec::new()); + }; - match tool_diagnostics { - Ok(diags) => { - for (entry_uri, mut diags) in diags { - aggregated.entry(entry_uri).or_default().append(&mut diags); - } - } - Err(err) => { - return Err(err); + run(tool, document) + }; + + match tool_diagnostics { + Ok(diags) => { + for (entry_uri, mut diags) in diags { + aggregated.entry(entry_uri).or_default().append(&mut diags); } } + Err(err) => { + return Err(err); + } } // In push mode, keep track of published diagnostics to clear them on shutdown @@ -184,15 +184,12 @@ impl WorkspaceWorker { /// - If the file is formattable, but no changes are made, an empty vector is returned /// - If a tool error occurs, an Err is returned pub async fn format_file(&self, document: &TextDocument<'_>) -> Result, String> { - for tool in self.tools.read().await.iter() { - let edits = tool.run_format(document)?; - // If no edits are made, continue to the next tool - if edits.is_empty() { - continue; - } - return Ok(edits); - } - Ok(Vec::new()) + let tool_guard = self.tool.read().await; + let Some(tool) = tool_guard.as_ref() else { + return Ok(Vec::new()); + }; + + tool.run_format(document) } /// Shutdown the worker and return any necessary changes to be made after shutdown. @@ -208,8 +205,9 @@ impl WorkspaceWorker { let uris_to_clear_diagnostics = self.published_diagnostics.lock().await.drain().collect::>(); let mut watchers_to_unregister = Vec::new(); - for (tool, builder) in self.tools.read().await.iter().zip(self.builders.iter()) { - builder.shutdown(&self.root_uri); + + if let Some(tool) = self.tool.read().await.as_ref() { + self.builder.shutdown(&self.root_uri); watchers_to_unregister .push(unregistration_tool_watcher_id(tool.name(), &self.root_uri)); @@ -227,7 +225,7 @@ impl WorkspaceWorker { context: &CodeActionContext, ) -> Vec { let mut actions = Vec::new(); - for tool in self.tools.read().await.iter() { + if let Some(tool) = self.tool.read().await.as_ref() { actions.extend(tool.get_code_actions_or_commands(uri, range, context)); } actions @@ -330,47 +328,43 @@ impl WorkspaceWorker { let mut unregistrations = vec![]; let mut diagnostics: Option)>> = None; - let mut tools = self.tools.write().await; - debug_assert_eq!( - tools.len(), - self.builders.len(), - "tools and builders must have the same length" - ); - for (tool, builder) in tools.iter_mut().zip(self.builders.iter()) { - let builder: &dyn ToolBuilder = builder.as_ref(); - let change = change_handler(tool, builder); - - if let Some(patterns) = change.watch_patterns { - unregistrations.push(unregistration_tool_watcher_id(tool.name(), &self.root_uri)); - if !patterns.is_empty() { - registrations.push(registration_tool_watcher_id( - tool.name(), - &self.root_uri, - patterns, - )); - } - } - if let Some(replaced_tool) = change.tool { - *tool = replaced_tool; - *needs_diagnostic_refresh = true; + let mut tools = self.tool.write().await; + let Some(tool) = tools.as_mut() else { + // No tool to update, return early + return (None, registrations, unregistrations); + }; + let change = change_handler(tool, self.builder.as_ref()); - let Some(file_system) = file_system else { + if let Some(patterns) = change.watch_patterns { + unregistrations.push(unregistration_tool_watcher_id(tool.name(), &self.root_uri)); + if !patterns.is_empty() { + registrations.push(registration_tool_watcher_id( + tool.name(), + &self.root_uri, + patterns, + )); + } + } + if let Some(replaced_tool) = change.tool { + *tool = replaced_tool; + *needs_diagnostic_refresh = true; + + let Some(file_system) = file_system else { + return (None, registrations, unregistrations); + }; + + for uri in file_system.keys() { + let document = file_system.get_document(&uri); + let Ok(mut reports) = tool.run_diagnostic(&document) else { + // If diagnostics could not be run, skip this URI, but continue with others + // TODO: Should we aggregate errors instead? One by one, or all together? continue; }; - - for uri in file_system.keys() { - let document = file_system.get_document(&uri); - let Ok(mut reports) = tool.run_diagnostic(&document) else { - // If diagnostics could not be run, skip this URI, but continue with others - // TODO: Should we aggregate errors instead? One by one, or all together? - continue; - }; - if !reports.is_empty() { - if let Some(existing_diagnostics) = &mut diagnostics { - existing_diagnostics.append(&mut reports); - } else { - diagnostics = Some(reports); - } + if !reports.is_empty() { + if let Some(existing_diagnostics) = &mut diagnostics { + existing_diagnostics.append(&mut reports); + } else { + diagnostics = Some(reports); } } } @@ -389,10 +383,12 @@ impl WorkspaceWorker { command: &str, arguments: Vec, ) -> Result, ErrorCode> { - for tool in self.tools.read().await.iter() { - if tool.is_responsible_for_command(command) { - return tool.execute_command(command, arguments); - } + let tool_guard = self.tool.read().await; + let Some(tool) = tool_guard.as_ref() else { + return Ok(None); + }; + if tool.is_responsible_for_command(command) { + return tool.execute_command(command, arguments); } Ok(None) } @@ -443,15 +439,15 @@ mod tests { worker::WorkspaceWorker, }; - fn create_builders() -> Arc<[Box]> { - Arc::new([Box::new(FakeToolBuilder::default()) as Box]) + fn create_builder() -> Arc { + Arc::new(FakeToolBuilder::default()) as Arc } #[test] fn test_get_root_uri() { let worker = WorkspaceWorker::new( Uri::from_str("file:///root/").unwrap(), - Arc::new([]), + create_builder(), DiagnosticMode::None, ); @@ -462,7 +458,7 @@ mod tests { async fn test_needs_init_options() { let worker = WorkspaceWorker::new( Uri::from_str("file:///root/").unwrap(), - Arc::new([]), + create_builder(), DiagnosticMode::None, ); assert!(worker.needs_init_options().await); @@ -475,7 +471,7 @@ mod tests { // with one watcher let worker = WorkspaceWorker::new( Uri::from_str("file:///root/").unwrap(), - create_builders(), + create_builder(), DiagnosticMode::None, ); worker.start_worker(serde_json::Value::Null).await; @@ -486,7 +482,7 @@ mod tests { // with no watchers let worker_no_watchers = WorkspaceWorker::new( Uri::from_str("file:///root/").unwrap(), - create_builders(), + create_builder(), DiagnosticMode::None, ); worker_no_watchers.start_worker(serde_json::json!({"some_option": true})).await; @@ -498,7 +494,7 @@ mod tests { async fn test_execute_command() { let worker = WorkspaceWorker::new( Uri::from_str("file:///root/").unwrap(), - create_builders(), + create_builder(), DiagnosticMode::None, ); worker.start_worker(serde_json::Value::Null).await; @@ -523,7 +519,7 @@ mod tests { async fn test_watched_files_change_notification() { let worker = WorkspaceWorker::new( Uri::from_str("file:///root/").unwrap(), - create_builders(), + create_builder(), DiagnosticMode::None, ); worker.start_worker(serde_json::Value::Null).await; @@ -612,7 +608,7 @@ mod tests { async fn test_did_change_configuration() { let worker = WorkspaceWorker::new( Uri::from_str("file:///root/").unwrap(), - create_builders(), + create_builder(), DiagnosticMode::None, ); worker.start_worker(serde_json::json!({"some_option": true})).await; @@ -674,7 +670,7 @@ mod tests { async fn test_code_action_collection() { let worker = WorkspaceWorker::new( Uri::from_str("file:///root/").unwrap(), - create_builders(), + create_builder(), DiagnosticMode::None, ); worker.start_worker(serde_json::Value::Null).await; @@ -709,7 +705,7 @@ mod tests { async fn test_run_diagnostic() { let worker = WorkspaceWorker::new( Uri::from_str("file:///root/").unwrap(), - create_builders(), + create_builder(), DiagnosticMode::None, ); let uri = Uri::from_str("file:///root/diagnostics.config").unwrap(); @@ -773,7 +769,7 @@ mod tests { async fn test_run_diagnostic_on_change() { let worker = WorkspaceWorker::new( Uri::from_str("file:///root/").unwrap(), - create_builders(), + create_builder(), DiagnosticMode::None, ); let uri = Uri::from_str("file:///root/diagnostics.config").unwrap(); @@ -837,7 +833,7 @@ mod tests { async fn test_run_diagnostic_on_save() { let worker = WorkspaceWorker::new( Uri::from_str("file:///root/").unwrap(), - create_builders(), + create_builder(), DiagnosticMode::None, ); let uri = Uri::from_str("file:///root/diagnostics.config").unwrap(); diff --git a/crates/oxc_language_server/src/worker_manager.rs b/crates/oxc_language_server/src/worker_manager.rs index bff28dfa924c1..37a55c18b0bb4 100644 --- a/crates/oxc_language_server/src/worker_manager.rs +++ b/crates/oxc_language_server/src/worker_manager.rs @@ -48,16 +48,16 @@ impl std::ops::Deref for WorkerGuard<'_> { pub struct WorkerManager { workers: RwLock>, single_file_mode: AtomicBool, - tool_builders: Arc<[Box]>, + tool_builder: Arc, } impl WorkerManager { /// Create a new [`WorkerManager`] with no workers and single-file mode disabled. - pub fn new(tool_builders: Arc<[Box]>) -> Self { + pub fn new(tool_builder: Arc) -> Self { Self { workers: RwLock::new(vec![]), single_file_mode: AtomicBool::new(false), - tool_builders, + tool_builder, } } @@ -68,9 +68,9 @@ impl WorkerManager { self.workers.read().await } - /// Iterate over the tool builders. - pub fn read_tool_builders(&self) -> &[Box] { - &self.tool_builders + /// Access the tool builder. + pub fn read_tool_builder(&self) -> &Arc { + &self.tool_builder } /// Returns `true` when the server was started without any workspace folders. @@ -98,7 +98,7 @@ impl WorkerManager { /// Build a new [`WorkspaceWorker`] for the given root URI without starting /// it. Call [`WorkspaceWorker::start_worker`] afterwards. pub fn create_worker(&self, root_uri: Uri, diagnostic_mode: DiagnosticMode) -> WorkspaceWorker { - WorkspaceWorker::new(root_uri, Arc::clone(&self.tool_builders), diagnostic_mode) + WorkspaceWorker::new(root_uri, Arc::clone(&self.tool_builder), diagnostic_mode) } // ── Lookup helpers (associated functions) ───────────────────────────────── @@ -265,7 +265,7 @@ impl WorkerManager { debug!("single file mode: creating workspace worker for {}", parent_uri.as_str()); let worker = - WorkspaceWorker::new(parent_uri, Arc::clone(&self.tool_builders), diagnostic_mode); + WorkspaceWorker::new(parent_uri, Arc::clone(&self.tool_builder), diagnostic_mode); worker.start_worker(Value::Null).await; let registrations = if dynamic_watchers { worker.init_watchers().await } else { vec![] }; @@ -336,18 +336,25 @@ mod tests { use tower_lsp_server::ls_types::Uri; - use crate::{DiagnosticMode, worker::WorkspaceWorker, worker_manager::WorkerManager}; + use crate::{ + DiagnosticMode, ToolBuilder, tests::FakeToolBuilder, worker::WorkspaceWorker, + worker_manager::WorkerManager, + }; + + fn create_builder() -> Arc { + Arc::new(FakeToolBuilder::default()) as Arc + } #[test] fn test_find_worker_for_uri_nested_workspaces() { let workspace = WorkspaceWorker::new( "file:///path/to/workspace".parse().unwrap(), - Arc::new([]), + create_builder(), DiagnosticMode::None, ); let workspace_deeper = WorkspaceWorker::new( "file:///path/to/workspace/deeper".parse().unwrap(), - Arc::new([]), + create_builder(), DiagnosticMode::None, ); let workers = vec![workspace, workspace_deeper]; @@ -374,12 +381,12 @@ mod tests { fn test_find_worker_for_uri_similar_names() { let workspace = WorkspaceWorker::new( "file:///path/to/workspace".parse().unwrap(), - Arc::new([]), + create_builder(), DiagnosticMode::None, ); let workspace2 = WorkspaceWorker::new( "file:///path/to/workspace-2".parse().unwrap(), - Arc::new([]), + create_builder(), DiagnosticMode::None, ); let workers = vec![workspace, workspace2]; @@ -401,7 +408,7 @@ mod tests { fn test_find_worker_for_uri_single_workspace() { let workspace = WorkspaceWorker::new( "file:///path/to/workspace".parse().unwrap(), - Arc::new([]), + create_builder(), DiagnosticMode::None, ); let workers = vec![workspace]; @@ -431,7 +438,7 @@ mod tests { fn test_find_worker_for_uri_vscode_user_data_single_workspace() { let workspace = WorkspaceWorker::new( "file:///path/to/workspace".parse().unwrap(), - Arc::new([]), + create_builder(), DiagnosticMode::None, ); let workers = vec![workspace]; @@ -447,7 +454,7 @@ mod tests { fn test_find_worker_for_uri_untitled_single_workspace() { let workspace = WorkspaceWorker::new( "file:///path/to/workspace".parse().unwrap(), - Arc::new([]), + create_builder(), DiagnosticMode::None, ); let workers = vec![workspace]; @@ -463,12 +470,12 @@ mod tests { fn test_find_worker_for_uri_untitled_multiple_workspaces() { let workspace1 = WorkspaceWorker::new( "file:///path/to/workspace1".parse().unwrap(), - Arc::new([]), + create_builder(), DiagnosticMode::None, ); let workspace2 = WorkspaceWorker::new( "file:///path/to/workspace2".parse().unwrap(), - Arc::new([]), + create_builder(), DiagnosticMode::None, ); let workers = vec![workspace1, workspace2]; @@ -494,12 +501,12 @@ mod tests { fn test_find_worker_for_uri_untitled_with_nested_workspaces() { let workspace = WorkspaceWorker::new( "file:///path/to/workspace".parse().unwrap(), - Arc::new([]), + create_builder(), DiagnosticMode::None, ); let workspace_deeper = WorkspaceWorker::new( "file:///path/to/workspace/deeper".parse().unwrap(), - Arc::new([]), + create_builder(), DiagnosticMode::None, ); let workers = vec![workspace, workspace_deeper];