diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index 6d62a68feb..9e21ff5adb 100644 --- a/cli/lsp/analysis.rs +++ b/cli/lsp/analysis.rs @@ -9,6 +9,7 @@ use std::path::Path; use deno_ast::SourceRange; use deno_ast::SourceRangedForSpanned; use deno_ast::SourceTextInfo; +use deno_core::anyhow::anyhow; use deno_core::error::AnyError; use deno_core::serde::Deserialize; use deno_core::serde::Serialize; @@ -620,9 +621,13 @@ fn try_reverse_map_package_json_exports( pub fn fix_ts_import_changes( changes: &[tsc::FileTextChanges], language_server: &language_server::Inner, + token: &CancellationToken, ) -> Result, AnyError> { let mut r = Vec::new(); for change in changes { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } let Ok(referrer) = ModuleSpecifier::parse(&change.file_name) else { continue; }; diff --git a/cli/lsp/code_lens.rs b/cli/lsp/code_lens.rs index bb72d0db77..b53380765d 100644 --- a/cli/lsp/code_lens.rs +++ b/cli/lsp/code_lens.rs @@ -11,6 +11,7 @@ use deno_ast::swc::visit::VisitWith; use deno_ast::ParsedSource; use deno_ast::SourceRange; use deno_ast::SourceRangedForSpanned; +use deno_core::anyhow::anyhow; use deno_core::error::AnyError; use deno_core::resolve_url; use deno_core::serde::Deserialize; @@ -21,7 +22,7 @@ use deno_core::ModuleSpecifier; use lazy_regex::lazy_regex; use once_cell::sync::Lazy; use regex::Regex; -use tower_lsp::jsonrpc::Error as LspError; +use tokio_util::sync::CancellationToken; use tower_lsp::lsp_types as lsp; use super::analysis::source_range_to_lsp_range; @@ -30,7 +31,6 @@ use super::language_server; use super::text::LineIndex; use super::tsc; use super::tsc::NavigationTree; -use crate::lsp::logging::lsp_warn; static ABSTRACT_MODIFIER: Lazy = lazy_regex!(r"\babstract\b"); @@ -253,6 +253,7 @@ async fn resolve_implementation_code_lens( code_lens: lsp::CodeLens, data: CodeLensData, language_server: &language_server::Inner, + token: &CancellationToken, ) -> Result { let asset_or_doc = language_server.get_asset_or_document(&data.specifier)?; let line_index = asset_or_doc.line_index(); @@ -262,15 +263,25 @@ async fn resolve_implementation_code_lens( language_server.snapshot(), data.specifier.clone(), line_index.offset_tsc(code_lens.range.start)?, + token, ) .await .map_err(|err| { - lsp_warn!("{err}"); - LspError::internal_error() + if token.is_cancelled() { + anyhow!("request cancelled") + } else { + anyhow!( + "Unable to get implementation locations from TypeScript: {:#}", + err + ) + } })?; if let Some(implementations) = maybe_implementations { let mut locations = Vec::new(); for implementation in implementations { + if token.is_cancelled() { + break; + } let implementation_specifier = resolve_url(&implementation.document_span.file_name)?; let implementation_location = @@ -326,10 +337,12 @@ async fn resolve_references_code_lens( code_lens: lsp::CodeLens, data: CodeLensData, language_server: &language_server::Inner, + token: &CancellationToken, ) -> Result { fn get_locations( maybe_referenced_symbols: Option>, language_server: &language_server::Inner, + token: &CancellationToken, ) -> Result, AnyError> { let symbols = match maybe_referenced_symbols { Some(symbols) => symbols, @@ -337,6 +350,9 @@ async fn resolve_references_code_lens( }; let mut locations = Vec::new(); for reference in symbols.iter().flat_map(|s| &s.references) { + if token.is_cancelled() { + break; + } if reference.is_definition { continue; } @@ -363,13 +379,18 @@ async fn resolve_references_code_lens( language_server.snapshot(), data.specifier.clone(), line_index.offset_tsc(code_lens.range.start)?, + token, ) .await .map_err(|err| { - lsp_warn!("Unable to find references: {err}"); - LspError::internal_error() + if token.is_cancelled() { + anyhow!("request cancelled") + } else { + anyhow!("Unable to get references from TypeScript: {:#}", err) + } })?; - let locations = get_locations(maybe_referenced_symbols, language_server)?; + let locations = + get_locations(maybe_referenced_symbols, language_server, token)?; let title = if locations.len() == 1 { "1 reference".to_string() } else { @@ -402,15 +423,18 @@ async fn resolve_references_code_lens( pub async fn resolve_code_lens( code_lens: lsp::CodeLens, language_server: &language_server::Inner, + token: &CancellationToken, ) -> Result { let data: CodeLensData = serde_json::from_value(code_lens.data.clone().unwrap())?; match data.source { CodeLensSource::Implementations => { - resolve_implementation_code_lens(code_lens, data, language_server).await + resolve_implementation_code_lens(code_lens, data, language_server, token) + .await } CodeLensSource::References => { - resolve_references_code_lens(code_lens, data, language_server).await + resolve_references_code_lens(code_lens, data, language_server, token) + .await } } } @@ -418,7 +442,9 @@ pub async fn resolve_code_lens( pub fn collect_test( specifier: &ModuleSpecifier, parsed_source: &ParsedSource, + _token: &CancellationToken, ) -> Result, AnyError> { + // TODO(nayeemrmn): Do cancellation checks while collecting tests. let mut collector = DenoTestCollector::new(specifier.clone(), parsed_source.clone()); parsed_source.program().visit_with(&mut collector); @@ -431,9 +457,10 @@ pub fn collect_tsc( code_lens_settings: &CodeLensSettings, line_index: Arc, navigation_tree: &NavigationTree, + token: &CancellationToken, ) -> Result, AnyError> { let code_lenses = Rc::new(RefCell::new(Vec::new())); - navigation_tree.walk(&|i, mp| { + navigation_tree.walk(token, &|i, mp| { let mut code_lenses = code_lenses.borrow_mut(); // TSC Implementations Code Lens @@ -541,7 +568,7 @@ pub fn collect_tsc( _ => (), } } - }); + })?; Ok(Rc::try_unwrap(code_lenses).unwrap().into_inner()) } diff --git a/cli/lsp/diagnostics.rs b/cli/lsp/diagnostics.rs index 3adbefd888..d713608e75 100644 --- a/cli/lsp/diagnostics.rs +++ b/cli/lsp/diagnostics.rs @@ -1087,7 +1087,7 @@ async fn generate_ts_diagnostics( let (ts_diagnostics_map, ambient_modules_by_scope) = if !enabled_specifiers.is_empty() { ts_server - .get_diagnostics(snapshot.clone(), enabled_specifiers, token) + .get_diagnostics(snapshot.clone(), enabled_specifiers, &token) .await? } else { Default::default() diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 3ff69e19f2..203b88f47f 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -32,6 +32,7 @@ use deno_lib::version::DENO_VERSION_INFO; use deno_path_util::url_to_file_path; use deno_runtime::deno_tls::rustls::RootCertStore; use deno_runtime::deno_tls::RootCertStoreProvider; +use deno_runtime::tokio_util::create_basic_runtime; use deno_semver::jsr::JsrPackageReqReference; use indexmap::Equivalent; use indexmap::IndexSet; @@ -43,6 +44,7 @@ use serde_json::from_value; use tokio::sync::mpsc::unbounded_channel; use tokio::sync::mpsc::UnboundedReceiver; use tokio::sync::mpsc::UnboundedSender; +use tokio_util::sync::CancellationToken; use tower_lsp::jsonrpc::Error as LspError; use tower_lsp::jsonrpc::Result as LspResult; use tower_lsp::lsp_types::request::*; @@ -137,6 +139,9 @@ pub struct LanguageServer { init_flag: AsyncFlag, performance: Arc, shutdown_flag: AsyncFlag, + /// This is used to move all request handling to a separate thread so blocking + /// code there won't block cancellations from being flagged. + worker_thread: Arc, } /// Snapshot of the state used by TSC. @@ -187,6 +192,73 @@ impl LanguageServerTaskQueue { } } +#[derive(Debug)] +struct WorkerThread { + runtime_handle: tokio::runtime::Handle, + shutdown_tx: Option>, + join_handle: Option>, +} + +impl WorkerThread { + fn create() -> Self { + let (handle_tx, handle_rx) = std::sync::mpsc::channel(); + let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel::<()>(); + let join_handle = std::thread::spawn(move || { + let runtime = create_basic_runtime(); + handle_tx.send(runtime.handle().clone()).unwrap(); + runtime.block_on(shutdown_rx).unwrap(); + }); + let runtime_handle = handle_rx.recv().unwrap(); + Self { + runtime_handle, + shutdown_tx: Some(shutdown_tx), + join_handle: Some(join_handle), + } + } + + async fn spawn( + &self, + fut: impl std::future::Future + Send + 'static, + ) { + self + .runtime_handle + .spawn(fut) + .await + .inspect_err(|err| lsp_warn!("Handler task error: {err}")) + .ok(); + } + + async fn spawn_with_cancellation< + T: Send + 'static, + TFut: std::future::Future> + Send, + >( + &self, + get_fut: impl FnOnce(CancellationToken) -> TFut + Send + 'static, + ) -> LspResult { + let token = CancellationToken::new(); + let _drop_guard = token.clone().drop_guard(); + self + .runtime_handle + .spawn(async move { + tokio::select! { + biased; + _ = token.cancelled() => Err(LspError::request_cancelled()), + result = get_fut(token.clone()) => result, + } + }) + .await + .inspect_err(|err| lsp_warn!("Handler task error: {err}")) + .unwrap_or_else(|_| Err(LspError::internal_error())) + } +} + +impl Drop for WorkerThread { + fn drop(&mut self) { + self.shutdown_tx.take().unwrap().send(()).unwrap(); + self.join_handle.take().unwrap().join().unwrap(); + } +} + #[derive(Debug)] pub struct Inner { /// Cached versions of "fixed" assets that can either be inlined in Rust or @@ -239,6 +311,7 @@ impl LanguageServer { init_flag: Default::default(), performance, shutdown_flag, + worker_thread: Arc::new(WorkerThread::create()), } } @@ -249,6 +322,22 @@ impl LanguageServer { specifiers: Vec, referrer: ModuleSpecifier, force_global_cache: bool, + ) -> LspResult> { + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |_| async move { + ls.cache_inner(specifiers, referrer, force_global_cache) + .await + }) + .await + } + + async fn cache_inner( + &self, + specifiers: Vec, + referrer: ModuleSpecifier, + force_global_cache: bool, ) -> LspResult> { async fn create_graph_for_caching( factory: CliFactory, @@ -349,29 +438,46 @@ impl LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - Ok( - self - .inner - .read() - .await - .diagnostics_server - .latest_batch_index() - .map(|v| v.into()), - ) + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |_| async move { + Ok( + ls.inner + .read() + .await + .diagnostics_server + .latest_batch_index() + .map(|v| v.into()), + ) + }) + .await } pub async fn performance_request(&self) -> LspResult> { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - Ok(Some(self.inner.read().await.get_performance())) + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |_| async move { + Ok(Some(ls.inner.read().await.get_performance())) + }) + .await } pub async fn task_definitions(&self) -> LspResult> { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.task_definitions() + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |_| async move { + ls.inner.read().await.task_definitions() + }) + .await } pub async fn test_run_request( @@ -381,7 +487,13 @@ impl LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.test_run_request(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |_| async move { + ls.inner.read().await.test_run_request(params).await + }) + .await } pub async fn test_run_cancel_request( @@ -391,7 +503,13 @@ impl LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.test_run_cancel_request(params) + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |_| async move { + ls.inner.read().await.test_run_cancel_request(params) + }) + .await } pub async fn virtual_text_document( @@ -401,22 +519,28 @@ impl LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - match params.map(serde_json::from_value) { - Some(Ok(params)) => Ok(Some( - serde_json::to_value( - self.inner.read().await.virtual_text_document(params)?, - ) - .map_err(|err| { - error!( - "Failed to serialize virtual_text_document response: {:#}", - err - ); - LspError::internal_error() - })?, - )), - Some(Err(err)) => Err(LspError::invalid_params(err.to_string())), - None => Err(LspError::invalid_params("Missing parameters")), - } + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |_| async move { + match params.map(serde_json::from_value) { + Some(Ok(params)) => Ok(Some( + serde_json::to_value( + ls.inner.read().await.virtual_text_document(params)?, + ) + .map_err(|err| { + error!( + "Failed to serialize virtual_text_document response: {:#}", + err + ); + LspError::internal_error() + })?, + )), + Some(Err(err)) => Err(LspError::invalid_params(err.to_string())), + None => Err(LspError::invalid_params("Missing parameters")), + } + }) + .await } pub async fn refresh_configuration(&self) { @@ -544,6 +668,7 @@ impl Inner { pub async fn get_navigation_tree( &self, specifier: &ModuleSpecifier, + token: &CancellationToken, ) -> Result, AnyError> { let mark = self.performance.mark_with_args( "lsp.get_navigation_tree", @@ -560,6 +685,7 @@ impl Inner { self.snapshot(), specifier.clone(), asset_or_doc.scope().cloned(), + token, ) .await?; let navigation_tree = Arc::new(navigation_tree); @@ -1306,6 +1432,7 @@ impl Inner { async fn document_symbol( &self, params: DocumentSymbolParams, + token: &CancellationToken, ) -> LspResult> { let specifier = self .url_map @@ -1322,18 +1449,27 @@ impl Inner { let asset_or_document = self.get_asset_or_document(&specifier)?; let line_index = asset_or_document.line_index(); - let navigation_tree = - self.get_navigation_tree(&specifier).await.map_err(|err| { - error!( - "Error getting document symbols for \"{}\": {:#}", - specifier, err - ); - LspError::internal_error() + let navigation_tree = self + .get_navigation_tree(&specifier, token) + .await + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!( + "Error getting navigation tree for \"{}\": {:#}", + specifier, err + ); + LspError::internal_error() + } })?; let response = if let Some(child_items) = &navigation_tree.child_items { let mut document_symbols = Vec::::new(); for item in child_items { + if token.is_cancelled() { + return Err(LspError::request_cancelled()); + } item .collect_document_symbols(line_index.clone(), &mut document_symbols); } @@ -1348,6 +1484,7 @@ impl Inner { async fn formatting( &self, params: DocumentFormattingParams, + _token: &CancellationToken, ) -> LspResult>> { let file_referrer = Some(uri_to_url(¶ms.text_document.uri)) .filter(|s| self.documents.is_valid_file_referrer(s)); @@ -1458,7 +1595,11 @@ impl Inner { } } - async fn hover(&self, params: HoverParams) -> LspResult> { + async fn hover( + &self, + params: HoverParams, + token: &CancellationToken, + ) -> LspResult> { let specifier = self.url_map.uri_to_specifier( ¶ms.text_document_position_params.text_document.uri, LspUrlKind::File, @@ -1536,8 +1677,17 @@ impl Inner { specifier.clone(), position, asset_or_doc.scope().cloned(), + token, ) - .await?; + .await + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!("Unable to get quick info from TypeScript: {:#}", err); + LspError::internal_error() + } + })?; maybe_quick_info.map(|qi| qi.to_hover(line_index, self)) }; self.performance.measure(mark); @@ -1589,6 +1739,7 @@ impl Inner { async fn code_action( &self, params: CodeActionParams, + token: &CancellationToken, ) -> LspResult> { let specifier = self .url_map @@ -1670,9 +1821,26 @@ impl Inner { &specifier, ), asset_or_doc.scope().cloned(), + token, ) - .await; + .await + .unwrap_or_else(|err| { + // sometimes tsc reports errors when retrieving code actions + // because they don't reflect the current state of the document + // so we will log them to the output, but we won't send an error + // message back to the client. + if !token.is_cancelled() { + error!( + "Unable to get code actions from TypeScript: {:#}", + err + ); + } + vec![] + }); for action in actions { + if token.is_cancelled() { + return Err(LspError::request_cancelled()); + } code_actions .add_ts_fix_action( &specifier, @@ -1776,13 +1944,35 @@ impl Inner { params.context.trigger_kind, only, asset_or_doc.scope().cloned(), + token, ) - .await?; - let mut refactor_actions = Vec::::new(); - for refactor_info in refactor_infos.iter() { - refactor_actions - .extend(refactor_info.to_code_actions(&specifier, ¶ms.range)); - } + .await + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!("Unable to get refactor info from TypeScript: {:#}", err); + LspError::internal_error() + } + })?; + let refactor_actions = refactor_infos + .into_iter() + .map(|refactor_info| { + refactor_info + .to_code_actions(&specifier, ¶ms.range, token) + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!("Unable to convert refactor info: {:#}", err); + LspError::internal_error() + } + }) + }) + .collect::, _>>()? + .into_iter() + .flatten() + .collect(); all_actions.extend( refactor::prune_invalid_actions(refactor_actions, 5) .into_iter() @@ -1808,6 +1998,7 @@ impl Inner { async fn code_action_resolve( &self, params: CodeAction, + token: &CancellationToken, ) -> LspResult { if params.kind.is_none() || params.data.is_none() { return Ok(params); @@ -1845,20 +2036,32 @@ impl Inner { &code_action_data.specifier, ), scope, + token, ) - .await?; + .await + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!("Unable to get combined fix from TypeScript: {:#}", err); + LspError::internal_error() + } + })?; if combined_code_actions.commands.is_some() { error!("Deno does not support code actions with commands."); return Err(LspError::invalid_request()); } let changes = if code_action_data.fix_id == "fixMissingImport" { - fix_ts_import_changes(&combined_code_actions.changes, self).map_err( - |err| { - error!("Unable to remap changes: {:#}", err); - LspError::internal_error() - }, - )? + fix_ts_import_changes(&combined_code_actions.changes, self, token) + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!("Unable to fix import changes: {:#}", err); + LspError::internal_error() + } + })? } else { combined_code_actions.changes }; @@ -1900,20 +2103,35 @@ impl Inner { &action_data.specifier, )), asset_or_doc.scope().cloned(), + token, ) - .await?; + .await + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!( + "Unable to get refactor edit info from TypeScript: {:#}", + err + ); + LspError::invalid_request() + } + })?; if kind_suffix == ".rewrite.function.returnType" || kind_suffix == ".move.newFile" { refactor_edit_info.edits = - fix_ts_import_changes(&refactor_edit_info.edits, self).map_err( - |err| { - error!("Unable to remap changes: {:#}", err); - LspError::internal_error() - }, - )? + fix_ts_import_changes(&refactor_edit_info.edits, self, token) + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!("Unable to fix import changes: {:#}", err); + LspError::internal_error() + } + })? } - code_action.edit = refactor_edit_info.to_workspace_edit(self)?; + code_action.edit = refactor_edit_info.to_workspace_edit(self, token)?; code_action } else { // The code action doesn't need to be resolved @@ -1946,6 +2164,7 @@ impl Inner { async fn code_lens( &self, params: CodeLensParams, + token: &CancellationToken, ) -> LspResult>> { let specifier = self .url_map @@ -1965,24 +2184,37 @@ impl Inner { { if let Some(Ok(parsed_source)) = asset_or_doc.maybe_parsed_source() { code_lenses.extend( - code_lens::collect_test(&specifier, parsed_source).map_err( + code_lens::collect_test(&specifier, parsed_source, token).map_err( |err| { - error!( - "Error getting test code lenses for \"{}\": {:#}", - &specifier, err - ); - LspError::internal_error() + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!( + "Error getting test code lenses for \"{}\": {:#}", + &specifier, err + ); + LspError::internal_error() + } }, )?, ); } } if settings.code_lens.implementations || settings.code_lens.references { - let navigation_tree = - self.get_navigation_tree(&specifier).await.map_err(|err| { - error!("Error getting code lenses for \"{}\": {:#}", specifier, err); + let navigation_tree = self + .get_navigation_tree(&specifier, token) + .await + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!( + "Error getting navigation tree for \"{}\": {:#}", + specifier, err + ); LspError::internal_error() - })?; + } + })?; let line_index = asset_or_doc.line_index(); code_lenses.extend( code_lens::collect_tsc( @@ -1990,13 +2222,18 @@ impl Inner { &settings.code_lens, line_index, &navigation_tree, + token, ) .map_err(|err| { - error!( - "Error getting ts code lenses for \"{:#}\": {:#}", - &specifier, err - ); - LspError::internal_error() + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!( + "Error getting ts code lenses for \"{:#}\": {:#}", + &specifier, err + ); + LspError::internal_error() + } })?, ); } @@ -2011,16 +2248,21 @@ impl Inner { async fn code_lens_resolve( &self, code_lens: CodeLens, + token: &CancellationToken, ) -> LspResult { let mark = self .performance .mark_with_args("lsp.code_lens_resolve", &code_lens); let result = if code_lens.data.is_some() { - code_lens::resolve_code_lens(code_lens, self) + code_lens::resolve_code_lens(code_lens, self, token) .await .map_err(|err| { - error!("Error resolving code lens: {:#}", err); - LspError::internal_error() + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!("Unable to get resolved code lens: {:#}", err); + LspError::internal_error() + } }) } else { Err(LspError::invalid_params( @@ -2034,6 +2276,7 @@ impl Inner { async fn document_highlight( &self, params: DocumentHighlightParams, + token: &CancellationToken, ) -> LspResult>> { let specifier = self.url_map.uri_to_specifier( ¶ms.text_document_position_params.text_document.uri, @@ -2059,25 +2302,47 @@ impl Inner { line_index.offset_tsc(params.text_document_position_params.position)?, files_to_search, asset_or_doc.scope().cloned(), + token, ) - .await?; + .await + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!( + "Unable to get document highlights from TypeScript: {:#}", + err + ); + LspError::internal_error() + } + })?; - if let Some(document_highlights) = maybe_document_highlights { - let result = document_highlights - .into_iter() - .flat_map(|dh| dh.to_highlight(line_index.clone())) - .collect(); - self.performance.measure(mark); - Ok(Some(result)) - } else { - self.performance.measure(mark); - Ok(None) - } + let document_highlights = maybe_document_highlights + .map(|document_highlights| { + document_highlights + .into_iter() + .map(|dh| { + dh.to_highlight(line_index.clone(), token).map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!("Unable to convert document highlights: {:#}", err); + LspError::internal_error() + } + }) + }) + .collect::, _>>() + .map(|s| s.into_iter().flatten().collect()) + }) + .transpose()?; + self.performance.measure(mark); + Ok(document_highlights) } async fn references( &self, params: ReferenceParams, + token: &CancellationToken, ) -> LspResult>> { let specifier = self.url_map.uri_to_specifier( ¶ms.text_document_position.text_document.uri, @@ -2098,16 +2363,24 @@ impl Inner { self.snapshot(), specifier.clone(), line_index.offset_tsc(params.text_document_position.position)?, + token, ) .await .map_err(|err| { - lsp_warn!("Unable to find references: {err}"); - LspError::internal_error() + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!("Unable to get references from TypeScript: {:#}", err); + LspError::internal_error() + } })?; if let Some(symbols) = maybe_referenced_symbols { let mut results = Vec::new(); for reference in symbols.iter().flat_map(|s| &s.references) { + if token.is_cancelled() { + return Err(LspError::request_cancelled()); + } if !params.context.include_declaration && reference.is_definition { continue; } @@ -2134,6 +2407,7 @@ impl Inner { async fn goto_definition( &self, params: GotoDefinitionParams, + token: &CancellationToken, ) -> LspResult> { let specifier = self.url_map.uri_to_specifier( ¶ms.text_document_position_params.text_document.uri, @@ -2157,11 +2431,30 @@ impl Inner { specifier, line_index.offset_tsc(params.text_document_position_params.position)?, asset_or_doc.scope().cloned(), + token, ) - .await?; + .await + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!("Unable to get definition info from TypeScript: {:#}", err); + LspError::internal_error() + } + })?; if let Some(definition) = maybe_definition { - let results = definition.to_definition(line_index, self); + let results = + definition + .to_definition(line_index, self, token) + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!("Unable to convert definition info: {:#}", err); + LspError::internal_error() + } + })?; self.performance.measure(mark); Ok(results) } else { @@ -2173,6 +2466,7 @@ impl Inner { async fn goto_type_definition( &self, params: GotoTypeDefinitionParams, + token: &CancellationToken, ) -> LspResult> { let specifier = self.url_map.uri_to_specifier( ¶ms.text_document_position_params.text_document.uri, @@ -2196,12 +2490,27 @@ impl Inner { specifier, line_index.offset_tsc(params.text_document_position_params.position)?, asset_or_doc.scope().cloned(), + token, ) - .await?; + .await + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!( + "Unable to get type definition info from TypeScript: {:#}", + err + ); + LspError::internal_error() + } + })?; let response = if let Some(definition_info) = maybe_definition_info { let mut location_links = Vec::new(); for info in definition_info { + if token.is_cancelled() { + return Err(LspError::request_cancelled()); + } if let Some(link) = info.document_span.to_link(line_index.clone(), self) { location_links.push(link); @@ -2219,6 +2528,7 @@ impl Inner { async fn completion( &self, params: CompletionParams, + token: &CancellationToken, ) -> LspResult> { let specifier = self.url_map.uri_to_specifier( ¶ms.text_document_position.text_document.uri, @@ -2300,27 +2610,40 @@ impl Inner { .options) .into(), scope.cloned(), + token, ) .await .unwrap_or_else(|err| { - error!("Unable to get completion info from TypeScript: {:#}", err); + if !token.is_cancelled() { + error!("Unable to get completion info from TypeScript: {:#}", err); + } None }); if let Some(completions) = maybe_completion_info { response = Some( - completions.as_completion_response( - line_index, - &self - .config - .language_settings_for_specifier(&specifier) - .cloned() - .unwrap_or_default() - .suggest, - &specifier, - position, - self, - ), + completions + .as_completion_response( + line_index, + &self + .config + .language_settings_for_specifier(&specifier) + .cloned() + .unwrap_or_default() + .suggest, + &specifier, + position, + self, + token, + ) + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!("Unable to convert completion info: {:#}", err); + LspError::internal_error() + } + })?, ); } }; @@ -2331,6 +2654,7 @@ impl Inner { async fn completion_resolve( &self, params: CompletionItem, + token: &CancellationToken, ) -> LspResult { let mark = self .performance @@ -2367,6 +2691,7 @@ impl Inner { ..data.into() }, scope, + token, ) .await; match result { @@ -2389,7 +2714,12 @@ impl Inner { } } Err(err) => { - error!("Unable to get completion info from TypeScript: {:#}", err); + if !token.is_cancelled() { + error!( + "Unable to get completion info from TypeScript: {:#}", + err + ); + } return Ok(params); } } @@ -2412,6 +2742,7 @@ impl Inner { async fn goto_implementation( &self, params: GotoImplementationParams, + token: &CancellationToken, ) -> LspResult> { let specifier = self.url_map.uri_to_specifier( ¶ms.text_document_position_params.text_document.uri, @@ -2435,16 +2766,27 @@ impl Inner { self.snapshot(), specifier, line_index.offset_tsc(params.text_document_position_params.position)?, + token, ) .await .map_err(|err| { - lsp_warn!("{:#}", err); - LspError::internal_error() + if token.is_cancelled() { + LspError::request_cancelled() + } else { + lsp_warn!( + "Unable to get implementation locations from TypeScript: {:#}", + err + ); + LspError::internal_error() + } })?; let result = if let Some(implementations) = maybe_implementations { let mut links = Vec::new(); for implementation in implementations { + if token.is_cancelled() { + return Err(LspError::request_cancelled()); + } if let Some(link) = implementation.to_link(line_index.clone(), self) { links.push(link) } @@ -2461,6 +2803,7 @@ impl Inner { async fn folding_range( &self, params: FoldingRangeParams, + token: &CancellationToken, ) -> LspResult>> { let specifier = self .url_map @@ -2482,21 +2825,33 @@ impl Inner { self.snapshot(), specifier, asset_or_doc.scope().cloned(), + token, ) - .await?; + .await + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + lsp_warn!("Unable to get outlining spans from TypeScript: {:#}", err); + LspError::invalid_request() + } + })?; let response = if !outlining_spans.is_empty() { Some( outlining_spans .iter() .map(|span| { - span.to_folding_range( + if token.is_cancelled() { + return Err(LspError::request_cancelled()); + } + Ok(span.to_folding_range( asset_or_doc.line_index(), asset_or_doc.text().as_bytes(), self.config.line_folding_only_capable(), - ) + )) }) - .collect::>(), + .collect::, _>>()?, ) } else { None @@ -2508,6 +2863,7 @@ impl Inner { async fn incoming_calls( &self, params: CallHierarchyIncomingCallsParams, + token: &CancellationToken, ) -> LspResult>> { let specifier = self .url_map @@ -2530,11 +2886,16 @@ impl Inner { self.snapshot(), specifier, line_index.offset_tsc(params.item.selection_range.start)?, + token, ) .await .map_err(|err| { - lsp_warn!("{:#}", err); - LspError::internal_error() + if token.is_cancelled() { + LspError::request_cancelled() + } else { + lsp_warn!("Unable to get incoming calls from TypeScript: {:#}", err); + LspError::internal_error() + } })?; let maybe_root_path_owned = self @@ -2543,6 +2904,9 @@ impl Inner { .and_then(|uri| url_to_file_path(uri).ok()); let mut resolved_items = Vec::::new(); for item in incoming_calls.iter() { + if token.is_cancelled() { + return Err(LspError::request_cancelled()); + } if let Some(resolved) = item.try_resolve_call_hierarchy_incoming_call( self, maybe_root_path_owned.as_deref(), @@ -2557,6 +2921,7 @@ impl Inner { async fn outgoing_calls( &self, params: CallHierarchyOutgoingCallsParams, + token: &CancellationToken, ) -> LspResult>> { let specifier = self .url_map @@ -2580,8 +2945,17 @@ impl Inner { specifier, line_index.offset_tsc(params.item.selection_range.start)?, asset_or_doc.scope().cloned(), + token, ) - .await?; + .await + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + lsp_warn!("Unable to get outgoing calls from TypeScript: {:#}", err); + LspError::invalid_request() + } + })?; let maybe_root_path_owned = self .config @@ -2589,6 +2963,9 @@ impl Inner { .and_then(|uri| url_to_file_path(uri).ok()); let mut resolved_items = Vec::::new(); for item in outgoing_calls.iter() { + if token.is_cancelled() { + return Err(LspError::request_cancelled()); + } if let Some(resolved) = item.try_resolve_call_hierarchy_outgoing_call( line_index.clone(), self, @@ -2604,6 +2981,7 @@ impl Inner { async fn prepare_call_hierarchy( &self, params: CallHierarchyPrepareParams, + token: &CancellationToken, ) -> LspResult>> { let specifier = self.url_map.uri_to_specifier( ¶ms.text_document_position_params.text_document.uri, @@ -2628,8 +3006,17 @@ impl Inner { specifier, line_index.offset_tsc(params.text_document_position_params.position)?, asset_or_doc.scope().cloned(), + token, ) - .await?; + .await + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + lsp_warn!("Unable to get call hierarchy from TypeScript: {:#}", err); + LspError::invalid_request() + } + })?; let response = if let Some(one_or_many) = maybe_one_or_many { let maybe_root_path_owned = self @@ -2648,6 +3035,9 @@ impl Inner { } tsc::OneOrMany::Many(items) => { for item in items.iter() { + if token.is_cancelled() { + return Err(LspError::request_cancelled()); + } if let Some(resolved) = item.try_resolve_call_hierarchy_item( self, maybe_root_path_owned.as_deref(), @@ -2668,6 +3058,7 @@ impl Inner { async fn rename( &self, params: RenameParams, + token: &CancellationToken, ) -> LspResult> { let specifier = self.url_map.uri_to_specifier( ¶ms.text_document_position.text_document.uri, @@ -2689,20 +3080,32 @@ impl Inner { self.snapshot(), specifier, line_index.offset_tsc(params.text_document_position.position)?, + token, ) .await .map_err(|err| { - lsp_warn!("{:#}", err); - LspError::internal_error() + if token.is_cancelled() { + LspError::request_cancelled() + } else { + lsp_warn!( + "Unable to get rename locations from TypeScript: {:#}", + err + ); + LspError::internal_error() + } })?; if let Some(locations) = maybe_locations { let rename_locations = tsc::RenameLocations { locations }; let workspace_edits = rename_locations - .into_workspace_edit(¶ms.new_name, self) + .into_workspace_edit(¶ms.new_name, self, token) .map_err(|err| { - error!("Failed to get workspace edits: {:#}", err); - LspError::internal_error() + if token.is_cancelled() { + LspError::request_cancelled() + } else { + lsp_warn!("Unable to covert rename locations: {:#}", err); + LspError::internal_error() + } })?; self.performance.measure(mark); Ok(Some(workspace_edits)) @@ -2715,6 +3118,7 @@ impl Inner { async fn selection_range( &self, params: SelectionRangeParams, + token: &CancellationToken, ) -> LspResult>> { let specifier = self .url_map @@ -2733,6 +3137,9 @@ impl Inner { let mut selection_ranges = Vec::::new(); for position in params.positions { + if token.is_cancelled() { + return Err(LspError::request_cancelled()); + } let selection_range: tsc::SelectionRange = self .ts_server .get_smart_selection_range( @@ -2740,8 +3147,20 @@ impl Inner { specifier.clone(), line_index.offset_tsc(position)?, asset_or_doc.scope().cloned(), + token, ) - .await?; + .await + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + lsp_warn!( + "Unable to get selection ranges from TypeScript: {:#}", + err + ); + LspError::invalid_request() + } + })?; selection_ranges .push(selection_range.to_selection_range(line_index.clone())); @@ -2753,6 +3172,7 @@ impl Inner { async fn semantic_tokens_full( &self, params: SemanticTokensParams, + token: &CancellationToken, ) -> LspResult> { let specifier = self .url_map @@ -2784,11 +3204,23 @@ impl Inner { specifier, 0..line_index.text_content_length_utf16().into(), asset_or_doc.scope().cloned(), + token, ) - .await?; + .await + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + lsp_warn!( + "Unable to get semantic classifications from TypeScript: {:#}", + err + ); + LspError::invalid_request() + } + })?; let semantic_tokens = - semantic_classification.to_semantic_tokens(line_index)?; + semantic_classification.to_semantic_tokens(line_index, token)?; if let Some(doc) = asset_or_doc.document() { doc.cache_semantic_tokens_full(semantic_tokens.clone()); @@ -2806,6 +3238,7 @@ impl Inner { async fn semantic_tokens_range( &self, params: SemanticTokensRangeParams, + token: &CancellationToken, ) -> LspResult> { let specifier = self .url_map @@ -2840,11 +3273,23 @@ impl Inner { line_index.offset_tsc(params.range.start)? ..line_index.offset_tsc(params.range.end)?, asset_or_doc.scope().cloned(), + token, ) - .await?; + .await + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + lsp_warn!( + "Unable to get semantic classifications from TypeScript: {:#}", + err + ); + LspError::invalid_request() + } + })?; let semantic_tokens = - semantic_classification.to_semantic_tokens(line_index)?; + semantic_classification.to_semantic_tokens(line_index, token)?; let response = if !semantic_tokens.data.is_empty() { Some(SemanticTokensRangeResult::Tokens(semantic_tokens)) } else { @@ -2857,6 +3302,7 @@ impl Inner { async fn signature_help( &self, params: SignatureHelpParams, + token: &CancellationToken, ) -> LspResult> { let specifier = self.url_map.uri_to_specifier( ¶ms.text_document_position_params.text_document.uri, @@ -2893,11 +3339,32 @@ impl Inner { line_index.offset_tsc(params.text_document_position_params.position)?, options, asset_or_doc.scope().cloned(), + token, ) - .await?; + .await + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + lsp_warn!( + "Unable to get signature help items from TypeScript: {:#}", + err + ); + LspError::invalid_request() + } + })?; if let Some(signature_help_items) = maybe_signature_help_items { - let signature_help = signature_help_items.into_signature_help(self); + let signature_help = signature_help_items + .into_signature_help(self, token) + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + lsp_warn!("Unable to convert signature help items: {:#}", err); + LspError::internal_error() + } + })?; self.performance.measure(mark); Ok(Some(signature_help)) } else { @@ -2909,6 +3376,7 @@ impl Inner { async fn will_rename_files( &self, params: RenameFilesParams, + token: &CancellationToken, ) -> LspResult> { let mut changes = vec![]; for rename in params.files { @@ -2947,20 +3415,29 @@ impl Inner { allow_text_changes_in_new_files: Some(true), ..Default::default() }, + token, ) .await .map_err(|err| { - lsp_warn!("{:#}", err); - LspError::internal_error() + if token.is_cancelled() { + LspError::request_cancelled() + } else { + lsp_warn!( + "Unable to get edits for file rename from TypeScript: {:#}", + err + ); + LspError::internal_error() + } })?, ); } - file_text_changes_to_workspace_edit(&changes, self) + file_text_changes_to_workspace_edit(&changes, self, token) } async fn symbol( &self, params: WorkspaceSymbolParams, + token: &CancellationToken, ) -> LspResult>> { let mark = self.performance.mark_with_args("lsp.symbol", ¶ms); @@ -2974,11 +3451,19 @@ impl Inner { max_result_count: Some(256), file: None, }, + token, ) .await .map_err(|err| { - error!("{:#}", err); - LspError::invalid_request() + if token.is_cancelled() { + LspError::request_cancelled() + } else { + lsp_warn!( + "Unable to get signature help items from TypeScript: {:#}", + err + ); + LspError::invalid_request() + } })?; let maybe_symbol_information = if navigate_to_items.is_empty() { @@ -2986,6 +3471,9 @@ impl Inner { } else { let mut symbol_information = Vec::new(); for item in navigate_to_items { + if token.is_cancelled() { + return Err(LspError::request_cancelled()); + } if let Some(info) = item.to_symbol_information(self) { symbol_information.push(info); } @@ -3133,28 +3621,52 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.write().await.did_open(params).await; + let ls = self.clone(); + self + .worker_thread + .spawn(async move { + ls.inner.write().await.did_open(params).await; + }) + .await; } async fn did_change(&self, params: DidChangeTextDocumentParams) { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.write().await.did_change(params).await + let ls = self.clone(); + self + .worker_thread + .spawn(async move { + ls.inner.write().await.did_change(params).await; + }) + .await; } async fn did_save(&self, params: DidSaveTextDocumentParams) { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.write().await.did_save(params); + let ls = self.clone(); + self + .worker_thread + .spawn(async move { + ls.inner.write().await.did_save(params); + }) + .await; } async fn did_close(&self, params: DidCloseTextDocumentParams) { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.write().await.did_close(params).await + let ls = self.clone(); + self + .worker_thread + .spawn(async move { + ls.inner.write().await.did_close(params).await; + }) + .await; } async fn did_change_configuration( @@ -3164,17 +3676,22 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - let mark = self - .performance - .mark_with_args("lsp.did_change_configuration", ¶ms); - self.refresh_configuration().await; + let ls = self.clone(); self - .inner - .write() - .await - .did_change_configuration(params) + .worker_thread + .spawn(async move { + let mark = ls + .performance + .mark_with_args("lsp.did_change_configuration", ¶ms); + ls.refresh_configuration().await; + ls.inner + .write() + .await + .did_change_configuration(params) + .await; + ls.performance.measure(mark); + }) .await; - self.performance.measure(mark); } async fn did_change_watched_files( @@ -3184,12 +3701,17 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } + let ls = self.clone(); self - .inner - .write() - .await - .did_change_watched_files(params) - .await + .worker_thread + .spawn(async move { + ls.inner + .write() + .await + .did_change_watched_files(params) + .await; + }) + .await; } async fn did_change_workspace_folders( @@ -3199,22 +3721,26 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - let mark = self - .performance - .mark_with_args("lsp.did_change_workspace_folders", ¶ms); + let ls = self.clone(); self - .inner - .write() - .await - .pre_did_change_workspace_folders(params); - self.refresh_configuration().await; - self - .inner - .write() - .await - .post_did_change_workspace_folders() + .worker_thread + .spawn(async move { + let mark = ls + .performance + .mark_with_args("lsp.did_change_workspace_folders", ¶ms); + ls.inner + .write() + .await + .pre_did_change_workspace_folders(params); + ls.refresh_configuration().await; + ls.inner + .write() + .await + .post_did_change_workspace_folders() + .await; + ls.performance.measure(mark); + }) .await; - self.performance.measure(mark); } async fn document_symbol( @@ -3224,7 +3750,13 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.document_symbol(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner.read().await.document_symbol(params, &token).await + }) + .await } async fn formatting( @@ -3234,14 +3766,26 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.formatting(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner.read().await.formatting(params, &token).await + }) + .await } async fn hover(&self, params: HoverParams) -> LspResult> { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.hover(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner.read().await.hover(params, &token).await + }) + .await } async fn inlay_hint( @@ -3251,7 +3795,13 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.inlay_hint(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner.read().await.inlay_hint(params, &token).await + }) + .await } async fn code_action( @@ -3261,7 +3811,13 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.code_action(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner.read().await.code_action(params, &token).await + }) + .await } async fn code_action_resolve( @@ -3271,7 +3827,17 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.code_action_resolve(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner + .read() + .await + .code_action_resolve(params, &token) + .await + }) + .await } async fn code_lens( @@ -3281,14 +3847,30 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.code_lens(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner.read().await.code_lens(params, &token).await + }) + .await } async fn code_lens_resolve(&self, params: CodeLens) -> LspResult { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.code_lens_resolve(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner + .read() + .await + .code_lens_resolve(params, &token) + .await + }) + .await } async fn document_highlight( @@ -3298,7 +3880,17 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.document_highlight(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner + .read() + .await + .document_highlight(params, &token) + .await + }) + .await } async fn references( @@ -3308,7 +3900,13 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.references(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner.read().await.references(params, &token).await + }) + .await } async fn goto_definition( @@ -3318,7 +3916,13 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.goto_definition(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner.read().await.goto_definition(params, &token).await + }) + .await } async fn goto_type_definition( @@ -3328,7 +3932,17 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.goto_type_definition(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner + .read() + .await + .goto_type_definition(params, &token) + .await + }) + .await } async fn completion( @@ -3338,7 +3952,13 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.completion(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner.read().await.completion(params, &token).await + }) + .await } async fn completion_resolve( @@ -3348,7 +3968,17 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.completion_resolve(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner + .read() + .await + .completion_resolve(params, &token) + .await + }) + .await } async fn goto_implementation( @@ -3358,7 +3988,17 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.goto_implementation(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner + .read() + .await + .goto_implementation(params, &token) + .await + }) + .await } async fn folding_range( @@ -3368,7 +4008,13 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.folding_range(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner.read().await.folding_range(params, &token).await + }) + .await } async fn incoming_calls( @@ -3378,7 +4024,13 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.incoming_calls(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner.read().await.incoming_calls(params, &token).await + }) + .await } async fn outgoing_calls( @@ -3388,7 +4040,13 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.outgoing_calls(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner.read().await.outgoing_calls(params, &token).await + }) + .await } async fn prepare_call_hierarchy( @@ -3398,7 +4056,17 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.prepare_call_hierarchy(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner + .read() + .await + .prepare_call_hierarchy(params, &token) + .await + }) + .await } async fn rename( @@ -3408,7 +4076,13 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.rename(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner.read().await.rename(params, &token).await + }) + .await } async fn selection_range( @@ -3418,7 +4092,13 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.selection_range(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner.read().await.selection_range(params, &token).await + }) + .await } async fn semantic_tokens_full( @@ -3428,7 +4108,17 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.semantic_tokens_full(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner + .read() + .await + .semantic_tokens_full(params, &token) + .await + }) + .await } async fn semantic_tokens_range( @@ -3438,7 +4128,17 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.semantic_tokens_range(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner + .read() + .await + .semantic_tokens_range(params, &token) + .await + }) + .await } async fn signature_help( @@ -3448,7 +4148,13 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.signature_help(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner.read().await.signature_help(params, &token).await + }) + .await } async fn will_rename_files( @@ -3458,7 +4164,17 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.will_rename_files(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner + .read() + .await + .will_rename_files(params, &token) + .await + }) + .await } async fn symbol( @@ -3468,7 +4184,13 @@ impl tower_lsp::LanguageServer for LanguageServer { if !self.init_flag.is_raised() { self.init_flag.wait_raised().await; } - self.inner.read().await.symbol(params).await + let ls = self.clone(); + self + .worker_thread + .spawn_with_cancellation(move |token| async move { + ls.inner.read().await.symbol(params, &token).await + }) + .await } } @@ -3770,6 +4492,7 @@ impl Inner { async fn inlay_hint( &self, params: InlayHintParams, + token: &CancellationToken, ) -> LspResult>> { let specifier = self .url_map @@ -3802,14 +4525,30 @@ impl Inner { &specifier, ), asset_or_doc.scope().cloned(), + token, ) - .await?; - let maybe_inlay_hints = maybe_inlay_hints.map(|hints| { - hints - .iter() - .map(|hint| hint.to_lsp(line_index.clone(), self)) - .collect() - }); + .await + .map_err(|err| { + if token.is_cancelled() { + LspError::request_cancelled() + } else { + error!("Unable to get inlay hints from TypeScript: {:#}", err); + LspError::internal_error() + } + })?; + let maybe_inlay_hints = maybe_inlay_hints + .map(|hints| { + hints + .into_iter() + .map(|hint| { + if token.is_cancelled() { + return Err(LspError::request_cancelled()); + } + Ok(hint.to_lsp(line_index.clone(), self)) + }) + .collect() + }) + .transpose()?; self.performance.measure(mark); Ok(maybe_inlay_hints) } diff --git a/cli/lsp/mod.rs b/cli/lsp/mod.rs index 6b5d17798b..9f11cc6990 100644 --- a/cli/lsp/mod.rs +++ b/cli/lsp/mod.rs @@ -82,7 +82,7 @@ pub async fn start() -> Result<(), AnyError> { // Force end the server 8 seconds after receiving a shutdown request. tokio::select! { biased; - _ = Server::new(stdin, stdout, socket).serve(service) => {} + _ = Server::new(stdin, stdout, socket).concurrency_level(32).serve(service) => {} _ = spawn(async move { shutdown_flag.wait_raised().await; tokio::time::sleep(std::time::Duration::from_secs(8)).await; diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index 280cd18cc9..62f6cdc584 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -471,7 +471,7 @@ impl TsServer { &self, snapshot: Arc, specifiers: Vec, - token: CancellationToken, + token: &CancellationToken, ) -> Result<(DiagnosticsMap, ScopedAmbientModules), AnyError> { let mut diagnostics_map = IndexMap::with_capacity(specifiers.len()); let mut specifiers_by_scope = BTreeMap::new(); @@ -497,27 +497,32 @@ impl TsServer { TscRequest::GetDiagnostics((specifiers, snapshot.project_version)); results.push_back( self - .request_with_cancellation::<(DiagnosticsMap, MaybeAmbientModules)>( + .request::<(DiagnosticsMap, MaybeAmbientModules)>( snapshot.clone(), req, scope.clone(), - token.clone(), + token, ) .map(|res| (scope, res)), ); } let mut ambient_modules_by_scope = HashMap::with_capacity(2); while let Some((scope, raw_diagnostics)) = results.next().await { - let (raw_diagnostics, ambient_modules) = raw_diagnostics - .inspect_err(|err| { - if !token.is_cancelled() { - lsp_warn!("Error generating TypeScript diagnostics: {err}"); - } - }) - .unwrap_or_default(); + if let Some(err) = raw_diagnostics.as_ref().err() { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } else { + lsp_warn!("Error generating TypeScript diagnostics: {err}"); + } + } + let (raw_diagnostics, ambient_modules) = + raw_diagnostics.unwrap_or_default(); for (mut specifier, mut diagnostics) in raw_diagnostics { specifier = self.specifier_map.normalize(&specifier)?.to_string(); for diagnostic in &mut diagnostics { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } normalize_diagnostic(diagnostic, &self.specifier_map)?; } diagnostics_map.insert(specifier, diagnostics); @@ -538,7 +543,12 @@ impl TsServer { { let req = TscRequest::CleanupSemanticCache; self - .request::<()>(snapshot.clone(), req, scope.cloned()) + .request::<()>( + snapshot.clone(), + req, + scope.cloned(), + &Default::default(), + ) .await .map_err(|err| { log::error!("Failed to request to tsserver {}", err); @@ -553,6 +563,7 @@ impl TsServer { snapshot: Arc, specifier: ModuleSpecifier, position: u32, + token: &CancellationToken, ) -> Result>, AnyError> { let req = TscRequest::FindReferences(( self.specifier_map.denormalize(&specifier), @@ -571,6 +582,7 @@ impl TsServer { snapshot.clone(), req.clone(), scope.cloned(), + token, )); } let mut all_symbols = IndexSet::new(); @@ -587,6 +599,9 @@ impl TsServer { continue; }; for symbol in &mut symbols { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } symbol.normalize(&self.specifier_map)?; } all_symbols.extend(symbols); @@ -602,11 +617,12 @@ impl TsServer { snapshot: Arc, specifier: ModuleSpecifier, scope: Option, + token: &CancellationToken, ) -> Result { let req = TscRequest::GetNavigationTree((self .specifier_map .denormalize(&specifier),)); - self.request(snapshot, req, scope).await + self.request(snapshot, req, scope, token).await } pub async fn get_supported_code_fixes( @@ -614,10 +630,13 @@ impl TsServer { snapshot: Arc, ) -> Result, LspError> { let req = TscRequest::GetSupportedCodeFixes; - self.request(snapshot, req, None).await.map_err(|err| { - log::error!("Unable to get fixable diagnostics: {}", err); - LspError::internal_error() - }) + self + .request(snapshot, req, None, &Default::default()) + .await + .map_err(|err| { + log::error!("Unable to get fixable diagnostics: {}", err); + LspError::internal_error() + }) } pub async fn get_quick_info( @@ -626,15 +645,13 @@ impl TsServer { specifier: ModuleSpecifier, position: u32, scope: Option, - ) -> Result, LspError> { + token: &CancellationToken, + ) -> Result, AnyError> { let req = TscRequest::GetQuickInfoAtPosition(( self.specifier_map.denormalize(&specifier), position, )); - self.request(snapshot, req, scope).await.map_err(|err| { - log::error!("Unable to get quick info: {}", err); - LspError::internal_error() - }) + self.request(snapshot, req, scope, token).await } #[allow(clippy::too_many_arguments)] @@ -647,7 +664,8 @@ impl TsServer { format_code_settings: FormatCodeSettings, preferences: UserPreferences, scope: Option, - ) -> Vec { + token: &CancellationToken, + ) -> Result, AnyError> { let req = TscRequest::GetCodeFixesAtPosition(Box::new(( self.specifier_map.denormalize(&specifier), range.start, @@ -656,26 +674,15 @@ impl TsServer { format_code_settings, preferences, ))); - let result = self - .request::>(snapshot, req, scope) + self + .request::>(snapshot, req, scope, token) .await .and_then(|mut actions| { for action in &mut actions { - action.normalize(&self.specifier_map)?; + action.normalize(&self.specifier_map, token)?; } Ok(actions) - }); - match result { - Ok(items) => items, - Err(err) => { - // sometimes tsc reports errors when retrieving code actions - // because they don't reflect the current state of the document - // so we will log them to the output, but we won't send an error - // message back to the client. - log::error!("Error getting actions from TypeScript: {}", err); - Vec::new() - } - } + }) } #[allow(clippy::too_many_arguments)] @@ -688,6 +695,7 @@ impl TsServer { trigger_kind: Option, only: String, scope: Option, + token: &CancellationToken, ) -> Result, LspError> { let trigger_kind = trigger_kind.map(|reason| match reason { lsp::CodeActionTriggerKind::INVOKED => "invoked", @@ -701,10 +709,13 @@ impl TsServer { trigger_kind, only, ))); - self.request(snapshot, req, scope).await.map_err(|err| { - log::error!("Failed to request to tsserver {}", err); - LspError::invalid_request() - }) + self + .request(snapshot, req, scope, token) + .await + .map_err(|err| { + log::error!("Failed to request to tsserver {}", err); + LspError::invalid_request() + }) } pub async fn get_combined_code_fix( @@ -714,7 +725,8 @@ impl TsServer { format_code_settings: FormatCodeSettings, preferences: UserPreferences, scope: Option, - ) -> Result { + token: &CancellationToken, + ) -> Result { let req = TscRequest::GetCombinedCodeFix(Box::new(( CombinedCodeFixScope { r#type: "file", @@ -725,16 +737,12 @@ impl TsServer { preferences, ))); self - .request::(snapshot, req, scope) + .request::(snapshot, req, scope, token) .await .and_then(|mut actions| { actions.normalize(&self.specifier_map)?; Ok(actions) }) - .map_err(|err| { - log::error!("Unable to get combined fix from TypeScript: {}", err); - LspError::internal_error() - }) } #[allow(clippy::too_many_arguments)] @@ -748,7 +756,8 @@ impl TsServer { action_name: String, preferences: Option, scope: Option, - ) -> Result { + token: &CancellationToken, + ) -> Result { let req = TscRequest::GetEditsForRefactor(Box::new(( self.specifier_map.denormalize(&specifier), format_code_settings, @@ -758,16 +767,12 @@ impl TsServer { preferences, ))); self - .request::(snapshot, req, scope) + .request::(snapshot, req, scope, token) .await .and_then(|mut info| { info.normalize(&self.specifier_map)?; Ok(info) }) - .map_err(|err| { - log::error!("Failed to request to tsserver {}", err); - LspError::invalid_request() - }) } pub async fn get_edits_for_file_rename( @@ -777,6 +782,7 @@ impl TsServer { new_specifier: ModuleSpecifier, format_code_settings: FormatCodeSettings, user_preferences: UserPreferences, + token: &CancellationToken, ) -> Result, AnyError> { let req = TscRequest::GetEditsForFileRename(Box::new(( self.specifier_map.denormalize(&old_specifier), @@ -797,20 +803,27 @@ impl TsServer { snapshot.clone(), req.clone(), scope.cloned(), + token, )); } let mut all_changes = IndexSet::new(); while let Some(changes) = results.next().await { - let mut changes = changes - .inspect_err(|err| { + if let Some(err) = changes.as_ref().err() { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } else { lsp_warn!( "Unable to get edits for file rename from TypeScript: {err}" ); - }) - .unwrap_or_default(); + } + } + let mut changes = changes.unwrap_or_default(); for changes in &mut changes { changes.normalize(&self.specifier_map)?; for text_changes in &mut changes.text_changes { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } text_changes.new_text = to_percent_decoded_str(&text_changes.new_text); } @@ -827,7 +840,8 @@ impl TsServer { position: u32, files_to_search: Vec, scope: Option, - ) -> Result>, LspError> { + token: &CancellationToken, + ) -> Result>, AnyError> { let req = TscRequest::GetDocumentHighlights(Box::new(( self.specifier_map.denormalize(&specifier), position, @@ -836,10 +850,7 @@ impl TsServer { .map(|s| self.specifier_map.denormalize(&s)) .collect::>(), ))); - self.request(snapshot, req, scope).await.map_err(|err| { - log::error!("Unable to get document highlights from TypeScript: {}", err); - LspError::internal_error() - }) + self.request(snapshot, req, scope, token).await } pub async fn get_definition( @@ -848,13 +859,16 @@ impl TsServer { specifier: ModuleSpecifier, position: u32, scope: Option, - ) -> Result, LspError> { + token: &CancellationToken, + ) -> Result, AnyError> { let req = TscRequest::GetDefinitionAndBoundSpan(( self.specifier_map.denormalize(&specifier), position, )); self - .request::>(snapshot, req, scope) + .request::>( + snapshot, req, scope, token, + ) .await .and_then(|mut info| { if let Some(info) = &mut info { @@ -862,10 +876,6 @@ impl TsServer { } Ok(info) }) - .map_err(|err| { - log::error!("Unable to get definition from TypeScript: {}", err); - LspError::internal_error() - }) } pub async fn get_type_definition( @@ -874,26 +884,27 @@ impl TsServer { specifier: ModuleSpecifier, position: u32, scope: Option, - ) -> Result>, LspError> { + token: &CancellationToken, + ) -> Result>, AnyError> { let req = TscRequest::GetTypeDefinitionAtPosition(( self.specifier_map.denormalize(&specifier), position, )); self - .request::>>(snapshot, req, scope) + .request::>>(snapshot, req, scope, token) .await .and_then(|mut infos| { for info in infos.iter_mut().flatten() { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } info.normalize(&self.specifier_map)?; } Ok(infos) }) - .map_err(|err| { - log::error!("Unable to get type definition from TypeScript: {}", err); - LspError::internal_error() - }) } + #[allow(clippy::too_many_arguments)] pub async fn get_completions( &self, snapshot: Arc, @@ -902,6 +913,7 @@ impl TsServer { options: GetCompletionsAtPositionOptions, format_code_settings: FormatCodeSettings, scope: Option, + token: &CancellationToken, ) -> Result, AnyError> { let req = TscRequest::GetCompletionsAtPosition(Box::new(( self.specifier_map.denormalize(&specifier), @@ -910,13 +922,13 @@ impl TsServer { format_code_settings, ))); self - .request::>(snapshot, req, scope) + .request::>(snapshot, req, scope, token) .await - .map(|mut info| { + .and_then(|mut info| { if let Some(info) = &mut info { - info.normalize(&self.specifier_map); + info.normalize(&self.specifier_map, token)?; } - info + Ok(info) }) } @@ -925,6 +937,7 @@ impl TsServer { snapshot: Arc, args: GetCompletionDetailsArgs, scope: Option, + token: &CancellationToken, ) -> Result, AnyError> { let req = TscRequest::GetCompletionEntryDetails(Box::new(( self.specifier_map.denormalize(&args.specifier), @@ -936,7 +949,7 @@ impl TsServer { args.data, ))); self - .request::>(snapshot, req, scope) + .request::>(snapshot, req, scope, token) .await .and_then(|mut details| { if let Some(details) = &mut details { @@ -951,6 +964,7 @@ impl TsServer { snapshot: Arc, specifier: ModuleSpecifier, position: u32, + token: &CancellationToken, ) -> Result>, AnyError> { let req = TscRequest::GetImplementationAtPosition(( self.specifier_map.denormalize(&specifier), @@ -969,6 +983,7 @@ impl TsServer { snapshot.clone(), req.clone(), scope.cloned(), + token, )); } let mut all_locations = IndexSet::new(); @@ -985,6 +1000,9 @@ impl TsServer { continue; }; for location in &mut locations { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } location.normalize(&self.specifier_map)?; } all_locations.extend(locations); @@ -1000,14 +1018,12 @@ impl TsServer { snapshot: Arc, specifier: ModuleSpecifier, scope: Option, - ) -> Result, LspError> { + token: &CancellationToken, + ) -> Result, AnyError> { let req = TscRequest::GetOutliningSpans((self .specifier_map .denormalize(&specifier),)); - self.request(snapshot, req, scope).await.map_err(|err| { - log::error!("Failed to request to tsserver {}", err); - LspError::invalid_request() - }) + self.request(snapshot, req, scope, token).await } pub async fn provide_call_hierarchy_incoming_calls( @@ -1015,6 +1031,7 @@ impl TsServer { snapshot: Arc, specifier: ModuleSpecifier, position: u32, + token: &CancellationToken, ) -> Result, AnyError> { let req = TscRequest::ProvideCallHierarchyIncomingCalls(( self.specifier_map.denormalize(&specifier), @@ -1033,19 +1050,23 @@ impl TsServer { snapshot.clone(), req.clone(), scope.cloned(), + token, )); } let mut all_calls = IndexSet::new(); while let Some(calls) = results.next().await { - let mut calls = calls - .inspect_err(|err| { - let err = err.to_string(); - if !err.contains("Could not find source file") { - lsp_warn!("Unable to get incoming calls from TypeScript: {err}"); - } - }) - .unwrap_or_default(); + if let Some(err) = calls.as_ref().err() { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } else { + lsp_warn!("Unable to get incoming calls from TypeScript: {err}"); + } + } + let mut calls = calls.unwrap_or_default(); for call in &mut calls { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } call.normalize(&self.specifier_map)?; } all_calls.extend(calls) @@ -1059,24 +1080,24 @@ impl TsServer { specifier: ModuleSpecifier, position: u32, scope: Option, - ) -> Result, LspError> { + token: &CancellationToken, + ) -> Result, AnyError> { let req = TscRequest::ProvideCallHierarchyOutgoingCalls(( self.specifier_map.denormalize(&specifier), position, )); self - .request::>(snapshot, req, scope) + .request::>(snapshot, req, scope, token) .await .and_then(|mut calls| { for call in &mut calls { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } call.normalize(&self.specifier_map)?; } Ok(calls) }) - .map_err(|err| { - log::error!("Failed to request to tsserver {}", err); - LspError::invalid_request() - }) } pub async fn prepare_call_hierarchy( @@ -1085,13 +1106,16 @@ impl TsServer { specifier: ModuleSpecifier, position: u32, scope: Option, - ) -> Result>, LspError> { + token: &CancellationToken, + ) -> Result>, AnyError> { let req = TscRequest::PrepareCallHierarchy(( self.specifier_map.denormalize(&specifier), position, )); self - .request::>>(snapshot, req, scope) + .request::>>( + snapshot, req, scope, token, + ) .await .and_then(|mut items| { match &mut items { @@ -1107,10 +1131,6 @@ impl TsServer { } Ok(items) }) - .map_err(|err| { - log::error!("Failed to request to tsserver {}", err); - LspError::invalid_request() - }) } pub async fn find_rename_locations( @@ -1118,6 +1138,7 @@ impl TsServer { snapshot: Arc, specifier: ModuleSpecifier, position: u32, + token: &CancellationToken, ) -> Result>, AnyError> { let req = TscRequest::FindRenameLocations(( self.specifier_map.denormalize(&specifier), @@ -1139,22 +1160,26 @@ impl TsServer { snapshot.clone(), req.clone(), scope.cloned(), + token, )); } let mut all_locations = IndexSet::new(); while let Some(locations) = results.next().await { - let locations = locations - .inspect_err(|err| { - let err = err.to_string(); - if !err.contains("Could not find source file") { - lsp_warn!("Unable to get rename locations from TypeScript: {err}"); - } - }) - .unwrap_or_default(); + if let Some(err) = locations.as_ref().err() { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } else { + lsp_warn!("Unable to get rename locations from TypeScript: {err}"); + } + } + let locations = locations.unwrap_or_default(); let Some(mut locations) = locations else { continue; }; for symbol in &mut locations { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } symbol.normalize(&self.specifier_map)?; } all_locations.extend(locations); @@ -1171,15 +1196,13 @@ impl TsServer { specifier: ModuleSpecifier, position: u32, scope: Option, - ) -> Result { + token: &CancellationToken, + ) -> Result { let req = TscRequest::GetSmartSelectionRange(( self.specifier_map.denormalize(&specifier), position, )); - self.request(snapshot, req, scope).await.map_err(|err| { - log::error!("Failed to request to tsserver {}", err); - LspError::invalid_request() - }) + self.request(snapshot, req, scope, token).await } pub async fn get_encoded_semantic_classifications( @@ -1188,7 +1211,8 @@ impl TsServer { specifier: ModuleSpecifier, range: Range, scope: Option, - ) -> Result { + token: &CancellationToken, + ) -> Result { let req = TscRequest::GetEncodedSemanticClassifications(( self.specifier_map.denormalize(&specifier), TextSpan { @@ -1197,10 +1221,7 @@ impl TsServer { }, "2020", )); - self.request(snapshot, req, scope).await.map_err(|err| { - log::error!("Failed to request to tsserver {}", err); - LspError::invalid_request() - }) + self.request(snapshot, req, scope, token).await } pub async fn get_signature_help_items( @@ -1210,22 +1231,21 @@ impl TsServer { position: u32, options: SignatureHelpItemsOptions, scope: Option, - ) -> Result, LspError> { + token: &CancellationToken, + ) -> Result, AnyError> { let req = TscRequest::GetSignatureHelpItems(( self.specifier_map.denormalize(&specifier), position, options, )); - self.request(snapshot, req, scope).await.map_err(|err| { - log::error!("Failed to request to tsserver: {}", err); - LspError::invalid_request() - }) + self.request(snapshot, req, scope, token).await } pub async fn get_navigate_to_items( &self, snapshot: Arc, args: GetNavigateToItemsArgs, + token: &CancellationToken, ) -> Result, AnyError> { let req = TscRequest::GetNavigateToItems(( args.search, @@ -1248,16 +1268,23 @@ impl TsServer { snapshot.clone(), req.clone(), scope.cloned(), + token, )); } let mut all_items = IndexSet::new(); while let Some(items) = results.next().await { - let mut items = items - .inspect_err(|err| { + if let Some(err) = items.as_ref().err() { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } else { lsp_warn!("Unable to get 'navigate to' items from TypeScript: {err}"); - }) - .unwrap_or_default(); + } + } + let mut items = items.unwrap_or_default(); for item in &mut items { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } item.normalize(&self.specifier_map)?; } all_items.extend(items) @@ -1272,16 +1299,14 @@ impl TsServer { text_span: TextSpan, user_preferences: UserPreferences, scope: Option, - ) -> Result>, LspError> { + token: &CancellationToken, + ) -> Result>, AnyError> { let req = TscRequest::ProvideInlayHints(( self.specifier_map.denormalize(&specifier), text_span, user_preferences, )); - self.request(snapshot, req, scope).await.map_err(|err| { - log::error!("Unable to get inlay hints: {}", err); - LspError::internal_error() - }) + self.request(snapshot, req, scope, token).await } async fn request( @@ -1289,6 +1314,7 @@ impl TsServer { snapshot: Arc, req: TscRequest, scope: Option, + token: &CancellationToken, ) -> Result where R: de::DeserializeOwned, @@ -1296,29 +1322,6 @@ impl TsServer { let mark = self .performance .mark(format!("tsc.request.{}", req.method())); - let r = self - .request_with_cancellation(snapshot, req, scope, Default::default()) - .await; - self.performance.measure(mark); - r - } - - async fn request_with_cancellation( - &self, - snapshot: Arc, - req: TscRequest, - scope: Option, - token: CancellationToken, - ) -> Result - where - R: de::DeserializeOwned, - { - // When an LSP request is cancelled by the client, the future this is being - // executed under and any local variables here will be dropped at the next - // await point. To pass on that cancellation to the TS thread, we use drop_guard - // which cancels the request's token on drop. - let token = token.child_token(); - let droppable_token = token.clone().drop_guard(); let (tx, mut rx) = oneshot::channel::>(); let change = self.pending_change.lock().take(); @@ -1332,8 +1335,9 @@ impl TsServer { tokio::select! { value = &mut rx => { let value = value??; - droppable_token.disarm(); - Ok(serde_json::from_str(&value)?) + let r = Ok(serde_json::from_str(&value)?); + self.performance.measure(mark); + r } _ = token.cancelled() => { Err(anyhow!("request cancelled")) @@ -1479,7 +1483,10 @@ async fn get_isolate_assets( state_snapshot: Arc, ) -> Vec { let req = TscRequest::GetAssets; - let res: Value = ts_server.request(state_snapshot, req, None).await.unwrap(); + let res: Value = ts_server + .request(state_snapshot, req, None, &Default::default()) + .await + .unwrap(); let response_assets = match res { Value::Array(value) => value, _ => unreachable!(), @@ -2432,28 +2439,45 @@ impl NavigationTree { !self.text.is_empty() && self.text != "" && self.text != "" } - pub fn walk(&self, callback: &F) + pub fn walk( + &self, + token: &CancellationToken, + callback: &F, + ) -> Result<(), AnyError> where F: Fn(&NavigationTree, Option<&NavigationTree>), { callback(self, None); if let Some(child_items) = &self.child_items { for child in child_items { - child.walk_child(callback, self); + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } + child.walk_child(token, callback, self)?; } } + Ok(()) } - fn walk_child(&self, callback: &F, parent: &NavigationTree) + fn walk_child( + &self, + token: &CancellationToken, + callback: &F, + parent: &NavigationTree, + ) -> Result<(), AnyError> where F: Fn(&NavigationTree, Option<&NavigationTree>), { callback(self, Some(parent)); if let Some(child_items) = &self.child_items { for child in child_items { - child.walk_child(callback, self); + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } + child.walk_child(token, callback, self)?; } } + Ok(()) } } @@ -2532,10 +2556,14 @@ impl RenameLocations { self, new_name: &str, language_server: &language_server::Inner, + token: &CancellationToken, ) -> Result { let mut text_document_edit_map = IndexMap::new(); let mut includes_non_files = false; for location in self.locations.iter() { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } let specifier = resolve_url(&location.document_span.file_name)?; if specifier.scheme() != "file" { includes_non_files = true; @@ -2653,10 +2681,14 @@ impl DefinitionInfoAndBoundSpan { &self, line_index: Arc, language_server: &language_server::Inner, - ) -> Option { + token: &CancellationToken, + ) -> Result, AnyError> { if let Some(definitions) = &self.definitions { let mut location_links = Vec::::new(); for di in definitions { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } if let Some(link) = di .document_span .to_link(line_index.clone(), language_server) @@ -2664,9 +2696,9 @@ impl DefinitionInfoAndBoundSpan { location_links.push(link); } } - Some(lsp::GotoDefinitionResponse::Link(location_links)) + Ok(Some(lsp::GotoDefinitionResponse::Link(location_links))) } else { - None + Ok(None) } } } @@ -2682,11 +2714,14 @@ impl DocumentHighlights { pub fn to_highlight( &self, line_index: Arc, - ) -> Vec { - self - .highlight_spans - .iter() - .map(|hs| lsp::DocumentHighlight { + token: &CancellationToken, + ) -> Result, AnyError> { + let mut highlights = Vec::with_capacity(self.highlight_spans.len()); + for hs in &self.highlight_spans { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } + highlights.push(lsp::DocumentHighlight { range: hs.text_span.to_range(line_index.clone()), kind: match hs.kind { HighlightSpanKind::WrittenReference => { @@ -2694,8 +2729,9 @@ impl DocumentHighlights { } _ => Some(lsp::DocumentHighlightKind::READ), }, - }) - .collect() + }); + } + Ok(highlights) } } @@ -2820,11 +2856,15 @@ impl Classifications { pub fn to_semantic_tokens( &self, line_index: Arc, + token: &CancellationToken, ) -> LspResult { // https://github.com/microsoft/vscode/blob/1.89.0/extensions/typescript-language-features/src/languageFeatures/semanticTokens.ts#L89-L115 let token_count = self.spans.len() / 3; let mut builder = SemanticTokensBuilder::new(); for i in 0..token_count { + if token.is_cancelled() { + return Err(LspError::request_cancelled()); + } let src_offset = 3 * i; let offset = self.spans[src_offset]; let length = self.spans[src_offset + 1]; @@ -2948,14 +2988,18 @@ impl ApplicableRefactorInfo { &self, specifier: &ModuleSpecifier, range: &lsp::Range, - ) -> Vec { + token: &CancellationToken, + ) -> Result, AnyError> { let mut code_actions = Vec::::new(); // All typescript refactoring actions are inlineable for action in self.actions.iter() { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } code_actions .push(self.as_inline_code_action(action, specifier, range, &self.name)); } - code_actions + Ok(code_actions) } fn as_inline_code_action( @@ -2993,9 +3037,13 @@ impl ApplicableRefactorInfo { pub fn file_text_changes_to_workspace_edit( changes: &[FileTextChanges], language_server: &language_server::Inner, + token: &CancellationToken, ) -> LspResult> { let mut all_ops = Vec::::new(); for change in changes { + if token.is_cancelled() { + return Err(LspError::request_cancelled()); + } let ops = match change.to_text_document_change_ops(language_server) { Ok(op) => op, Err(err) => { @@ -3034,8 +3082,9 @@ impl RefactorEditInfo { pub fn to_workspace_edit( &self, language_server: &language_server::Inner, + token: &CancellationToken, ) -> LspResult> { - file_text_changes_to_workspace_edit(&self.edits, language_server) + file_text_changes_to_workspace_edit(&self.edits, language_server, token) } } @@ -3085,8 +3134,12 @@ impl CodeFixAction { fn normalize( &mut self, specifier_map: &TscSpecifierMap, + token: &CancellationToken, ) -> Result<(), AnyError> { for changes in &mut self.changes { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } changes.normalize(specifier_map)?; } Ok(()) @@ -3686,10 +3739,18 @@ pub struct CompletionInfo { } impl CompletionInfo { - fn normalize(&mut self, specifier_map: &TscSpecifierMap) { + fn normalize( + &mut self, + specifier_map: &TscSpecifierMap, + token: &CancellationToken, + ) -> Result<(), AnyError> { for entry in &mut self.entries { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } entry.normalize(specifier_map); } + Ok(()) } pub fn as_completion_response( @@ -3699,26 +3760,29 @@ impl CompletionInfo { specifier: &ModuleSpecifier, position: u32, language_server: &language_server::Inner, - ) -> lsp::CompletionResponse { + token: &CancellationToken, + ) -> Result { // A cache for costly resolution computations. // On a test project, it was found to speed up completion requests // by 10-20x and contained ~300 entries for 8000 completion items. let mut cache = HashMap::with_capacity(512); - let items = self - .entries - .iter() - .flat_map(|entry| { - entry.as_completion_item( - line_index.clone(), - self, - settings, - specifier, - position, - language_server, - &mut cache, - ) - }) - .collect(); + let mut items = Vec::with_capacity(self.entries.len()); + for entry in &self.entries { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } + if let Some(item) = entry.as_completion_item( + line_index.clone(), + self, + settings, + specifier, + position, + language_server, + &mut cache, + ) { + items.push(item); + } + } let is_incomplete = self .metadata .clone() @@ -3731,10 +3795,10 @@ impl CompletionInfo { .unwrap() }) .unwrap_or(false); - lsp::CompletionResponse::List(lsp::CompletionList { + Ok(lsp::CompletionResponse::List(lsp::CompletionList { is_incomplete, items, - }) + })) } } @@ -4207,16 +4271,23 @@ impl SignatureHelpItems { pub fn into_signature_help( self, language_server: &language_server::Inner, - ) -> lsp::SignatureHelp { - lsp::SignatureHelp { - signatures: self - .items - .into_iter() - .map(|item| item.into_signature_information(language_server)) - .collect(), + token: &CancellationToken, + ) -> Result { + let signatures = self + .items + .into_iter() + .map(|item| { + if token.is_cancelled() { + return Err(anyhow!("request cancelled")); + } + Ok(item.into_signature_information(language_server)) + }) + .collect::>()?; + Ok(lsp::SignatureHelp { + signatures, active_parameter: Some(self.argument_index), active_signature: Some(self.selected_item_index), - } + }) } } @@ -5724,7 +5795,7 @@ mod tests { .await; let specifier = temp_dir.url().join("a.ts").unwrap(); let (diagnostics, _) = ts_server - .get_diagnostics(snapshot, vec![specifier.clone()], Default::default()) + .get_diagnostics(snapshot, vec![specifier.clone()], &Default::default()) .await .unwrap(); assert_eq!( @@ -5770,7 +5841,7 @@ mod tests { .await; let specifier = temp_dir.url().join("a.ts").unwrap(); let (diagnostics, _) = ts_server - .get_diagnostics(snapshot, vec![specifier.clone()], Default::default()) + .get_diagnostics(snapshot, vec![specifier.clone()], &Default::default()) .await .unwrap(); assert_eq!(json!(diagnostics), json!({ specifier: [] })); @@ -5800,7 +5871,7 @@ mod tests { .await; let specifier = temp_dir.url().join("a.ts").unwrap(); let (diagnostics, _ambient) = ts_server - .get_diagnostics(snapshot, vec![specifier.clone()], Default::default()) + .get_diagnostics(snapshot, vec![specifier.clone()], &Default::default()) .await .unwrap(); assert_eq!(json!(diagnostics), json!({ specifier: [] })); @@ -5826,7 +5897,7 @@ mod tests { .await; let specifier = temp_dir.url().join("a.ts").unwrap(); let (diagnostics, _ambient) = ts_server - .get_diagnostics(snapshot, vec![specifier.clone()], Default::default()) + .get_diagnostics(snapshot, vec![specifier.clone()], &Default::default()) .await .unwrap(); assert_eq!( @@ -5876,7 +5947,7 @@ mod tests { .await; let specifier = temp_dir.url().join("a.ts").unwrap(); let (diagnostics, _ambient) = ts_server - .get_diagnostics(snapshot, vec![specifier.clone()], Default::default()) + .get_diagnostics(snapshot, vec![specifier.clone()], &Default::default()) .await .unwrap(); assert_eq!(json!(diagnostics), json!({ specifier: [] })); @@ -5909,7 +5980,7 @@ mod tests { .await; let specifier = temp_dir.url().join("a.ts").unwrap(); let (diagnostics, _ambient) = ts_server - .get_diagnostics(snapshot, vec![specifier.clone()], Default::default()) + .get_diagnostics(snapshot, vec![specifier.clone()], &Default::default()) .await .unwrap(); assert_eq!( @@ -5967,7 +6038,7 @@ mod tests { .await; let specifier = temp_dir.url().join("a.ts").unwrap(); let (diagnostics, _ambient) = ts_server - .get_diagnostics(snapshot, vec![specifier.clone()], Default::default()) + .get_diagnostics(snapshot, vec![specifier.clone()], &Default::default()) .await .unwrap(); assert_eq!( @@ -6076,7 +6147,7 @@ mod tests { .get_diagnostics( snapshot.clone(), vec![specifier.clone()], - Default::default(), + &Default::default(), ) .await .unwrap(); @@ -6126,7 +6197,7 @@ mod tests { .get_diagnostics( snapshot.clone(), vec![specifier.clone()], - Default::default(), + &Default::default(), ) .await .unwrap(); @@ -6208,6 +6279,7 @@ mod tests { }, Default::default(), Some(temp_dir.url()), + &Default::default(), ) .await .unwrap() @@ -6226,6 +6298,7 @@ mod tests { data: None, }, Some(temp_dir.url()), + &Default::default(), ) .await .unwrap() @@ -6401,6 +6474,7 @@ mod tests { }, FormatCodeSettings::from(&fmt_options_config), Some(temp_dir.url()), + &Default::default(), ) .await .unwrap() @@ -6428,6 +6502,7 @@ mod tests { data: entry.data.clone(), }, Some(temp_dir.url()), + &Default::default(), ) .await .unwrap() @@ -6451,8 +6526,9 @@ mod tests { let classifications = Classifications { spans: vec![2, 6, 2057], }; - let semantic_tokens = - classifications.to_semantic_tokens(line_index).unwrap(); + let semantic_tokens = classifications + .to_semantic_tokens(line_index, &Default::default()) + .unwrap(); assert_eq!( &semantic_tokens.data, &[ @@ -6495,6 +6571,7 @@ mod tests { temp_dir.url().join("🦕.ts").unwrap(), FormatCodeSettings::default(), UserPreferences::default(), + &Default::default(), ) .await .unwrap(); diff --git a/tests/integration/lsp_tests.rs b/tests/integration/lsp_tests.rs index 46d2f16809..e3e7bb6124 100644 --- a/tests/integration/lsp_tests.rs +++ b/tests/integration/lsp_tests.rs @@ -209,6 +209,7 @@ fn unadded_dependency_message_with_import_map() { "arguments": [[], temp_dir.url().join("file.ts").unwrap()], }), ); + client.read_diagnostics(); let diagnostics = client.did_open(json!({ "textDocument": { @@ -268,6 +269,7 @@ fn unadded_dependency_message() { "arguments": [[], temp_dir.url().join("file.ts").unwrap()], }), ); + client.read_diagnostics(); let diagnostics = client.did_open(json!({ "textDocument": { @@ -381,6 +383,7 @@ fn lsp_import_map_remote() { "arguments": [[], temp_dir.url().join("file.ts").unwrap()], }), ); + client.read_diagnostics(); let diagnostics = client.did_open(json!({ "textDocument": { @@ -639,6 +642,7 @@ fn lsp_import_map_config_file_auto_discovered() { client.wait_until_stderr_line(|line| { line.contains(" Resolved Deno configuration file:") }); + client.read_diagnostics(); let uri = temp_dir.url().join("a.ts").unwrap(); @@ -700,6 +704,7 @@ fn lsp_import_map_config_file_auto_discovered() { client.wait_until_stderr_line(|line| { line.contains(" Resolved Deno configuration file:") }); + assert_eq!(json!(client.read_diagnostics().all()), json!([])); let res = client.write_request( "textDocument/hover", json!({ @@ -725,7 +730,6 @@ fn lsp_import_map_config_file_auto_discovered() { } }) ); - assert_eq!(client.read_diagnostics().all().len(), 0); client.shutdown(); } @@ -761,6 +765,7 @@ fn lsp_import_map_config_file_auto_discovered_symlink() { "type": 2 }] })); + client.read_diagnostics(); // this will discover the deno.json in the root let search_line = format!( @@ -826,6 +831,7 @@ fn lsp_deno_json_imports_comments_cache() { "arguments": [[], temp_dir.url().join("file.ts").unwrap()], }), ); + client.read_diagnostics(); let diagnostics = client.did_open(json!({ "textDocument": { @@ -917,6 +923,7 @@ fn lsp_format_vendor_path() { "arguments": [[], temp_dir.url().join("file.ts").unwrap()], }), ); + client.read_diagnostics(); assert!(temp_dir .path() .join("vendor/http_localhost_4545/run/002_hello.ts") @@ -1114,6 +1121,7 @@ fn lsp_did_refresh_deno_configuration_tree_notification() { "type": 1, }], })); + client.read_diagnostics(); let res = client .read_notification_with_method::( "deno/didRefreshDenoConfigurationTree", @@ -1263,6 +1271,7 @@ fn lsp_did_change_deno_configuration_notification() { "type": 2, }], })); + client.read_diagnostics(); let res = client .read_notification_with_method::("deno/didChangeDenoConfiguration"); assert_eq!( @@ -1284,6 +1293,7 @@ fn lsp_did_change_deno_configuration_notification() { "type": 3, }], })); + client.read_diagnostics(); let res = client .read_notification_with_method::("deno/didChangeDenoConfiguration"); assert_eq!( @@ -1305,6 +1315,7 @@ fn lsp_did_change_deno_configuration_notification() { "type": 2, }], })); + client.read_diagnostics(); let res = client .read_notification_with_method::("deno/didChangeDenoConfiguration"); assert_eq!( @@ -1326,6 +1337,7 @@ fn lsp_did_change_deno_configuration_notification() { "type": 3, }], })); + client.read_diagnostics(); let res = client .read_notification_with_method::("deno/didChangeDenoConfiguration"); assert_eq!( @@ -1409,7 +1421,7 @@ fn lsp_import_attributes() { }, })); - client.did_open(json!({ + client.did_open_raw(json!({ "textDocument": { "uri": "file:///a/test.json", "languageId": "json", @@ -2086,7 +2098,6 @@ fn lsp_suggestion_actions_disabled() { }, }, })); - client.read_diagnostics(); let diagnostics = client.did_open(json!({ "textDocument": { "uri": temp_dir.url().join("file.ts").unwrap(), @@ -2778,6 +2789,7 @@ fn lsp_hover_dependency() { "arguments": [[], "file:///a/file.ts"], }), ); + client.read_diagnostics(); let res = client.write_request( "textDocument/hover", json!({ @@ -3162,6 +3174,7 @@ fn lsp_hover_typescript_types() { ], }), ); + client.read_diagnostics(); let res = client.write_request( "textDocument/hover", json!({ @@ -3211,6 +3224,7 @@ fn lsp_hover_jsr() { "arguments": [[], temp_dir.url().join("file.ts").unwrap()], }), ); + client.read_diagnostics(); let res = client.write_request( "textDocument/hover", json!({ @@ -5733,6 +5747,7 @@ fn lsp_jsr_auto_import_completion() { ], }), ); + client.read_diagnostics(); client.did_open(json!({ "textDocument": { "uri": temp_dir.url().join("file.ts").unwrap(), @@ -5812,6 +5827,7 @@ fn lsp_jsr_auto_import_completion_import_map() { ], }), ); + client.read_diagnostics(); client.did_open(json!({ "textDocument": { "uri": temp_dir.url().join("file.ts").unwrap(), @@ -5951,6 +5967,7 @@ fn lsp_jsr_code_action_missing_declaration() { "arguments": [[], file.url()], }), ); + client.read_diagnostics(); client.did_open_file(&file); let res = client.write_request( "textDocument/codeAction", @@ -6071,6 +6088,7 @@ fn lsp_jsr_code_action_move_to_new_file() { "arguments": [[], file.url()], }), ); + client.read_diagnostics(); client.did_open_file(&file); let list = client .write_request_with_res_as::>( @@ -6490,6 +6508,7 @@ fn lsp_code_actions_deno_types_for_npm() { ], }), ); + client.read_diagnostics(); let res = client.write_request( "textDocument/codeAction", json!({ @@ -6657,6 +6676,7 @@ fn lsp_cache_then_definition() { ], }), ); + client.read_diagnostics(); let res = client.write_request( "textDocument/definition", json!({ @@ -7606,6 +7626,7 @@ fn lsp_quote_style_from_workspace_settings() { "type": 1, }], })); + client.read_diagnostics(); let res = client.write_request("textDocument/codeAction", code_action_params); // Expect double quotes in the auto-import. @@ -8090,6 +8111,7 @@ fn lsp_completions_auto_import_node_builtin() { "arguments": [[], temp_dir.url().join("file.ts").unwrap()], }), ); + client.read_diagnostics(); let list = client.get_completion_list( temp_dir.url().join("file.ts").unwrap(), (2, 21), @@ -8158,6 +8180,7 @@ fn lsp_npm_completions_auto_import_and_quick_fix_no_import_map() { ], }), ); + client.read_diagnostics(); // try auto-import with path client.did_open(json!({ @@ -8883,6 +8906,7 @@ fn lsp_npm_types_nested_js_dts() { "arguments": [[], file.url()], }), ); + client.read_diagnostics(); let diagnostics = client.did_open_file(&file); assert_eq!( json!(diagnostics.all()), @@ -8982,6 +9006,7 @@ fn lsp_npm_always_caches() { ], }), ); + client.read_diagnostics(); // now open a new file and chalk should be working let new_file_uri = temp_dir_path.join("new_file.ts").url_file(); @@ -9104,6 +9129,7 @@ fn lsp_completions_auto_import_and_quick_fix_with_import_map() { ], }), ); + client.read_diagnostics(); // try auto-import with path client.did_open(json!({ @@ -9742,6 +9768,7 @@ fn lsp_completions_npm() { ], }), ); + client.read_diagnostics(); // check importing a cjs default import client.write_notification( @@ -9955,6 +9982,7 @@ fn lsp_auto_imports_remote_dts() { "arguments": [[], temp_dir.url().join("file.ts").unwrap()], }), ); + client.read_diagnostics(); let list = client.get_completion_list( temp_dir.url().join("file.ts").unwrap(), (2, 21), @@ -10257,6 +10285,7 @@ fn lsp_completions_node_builtin() { "arguments": [["npm:@types/node"], "file:///a/file.ts"], }), ); + client.read_diagnostics(); client.write_notification( "textDocument/didChange", @@ -10324,6 +10353,7 @@ fn lsp_completions_node_specifier_node_modules_dir() { "arguments": [[], temp_dir.url().join("file.ts").unwrap()], }), ); + client.read_diagnostics(); client.write_notification( "textDocument/didChange", json!({ @@ -10559,6 +10589,7 @@ fn lsp_cache_location() { "arguments": [[], "file:///a/file.ts"], }), ); + client.read_diagnostics(); let res = client.write_request( "textDocument/hover", json!({ @@ -10653,6 +10684,7 @@ fn lsp_tls_cert() { "arguments": [[], "file:///a/file.ts"], }), ); + client.read_diagnostics(); let res = client.write_request( "textDocument/hover", json!({ @@ -10751,6 +10783,7 @@ fn lsp_npmrc() { "arguments": [[], file.url()], }), ); + client.read_diagnostics(); let diagnostics = client.did_open_file(&file); assert_eq!( json!(diagnostics.all()), @@ -11147,6 +11180,7 @@ fn lsp_root_with_global_reference_types() { "arguments": [[], file2.url()], }), ); + client.read_diagnostics(); let diagnostics = client.did_open_file(&file); assert_eq!(json!(diagnostics.all()), json!([])); } @@ -11456,6 +11490,7 @@ fn lsp_performance() { "tsc.op.op_load", "tsc.op.op_script_names", "tsc.request.$getAssets", + "tsc.request.$getDiagnostics", "tsc.request.$getSupportedCodeFixes", "tsc.request.getQuickInfoAtPosition", ] @@ -13187,6 +13222,7 @@ export function B() { ], }), ); + client.read_diagnostics(); let res = client.write_request( "textDocument/hover", json!({ @@ -13209,10 +13245,6 @@ export function B() { } }) ); - - let diagnostics = client.read_diagnostics(); - println!("{:?}", diagnostics); - client.shutdown(); } @@ -14035,6 +14067,7 @@ fn lsp_node_modules_dir() { "arguments": [["npm:chalk", "npm:@types/node"], file_uri], }), ); + client.read_diagnostics() }; cache(&mut client); @@ -14068,11 +14101,9 @@ fn lsp_node_modules_dir() { "imports": {}, }, "unstable": [], - } })); + } })) }; - refresh_config(&mut client); - - let diagnostics = client.read_diagnostics(); + let diagnostics = refresh_config(&mut client); assert_eq!(diagnostics.all().len(), 2, "{:#?}", diagnostics); // not cached cache(&mut client); @@ -14087,9 +14118,7 @@ fn lsp_node_modules_dir() { "{ \"nodeModulesDir\": \"auto\" }\n", ); refresh_config(&mut client); - cache(&mut client); - - let diagnostics = client.read_diagnostics(); + let diagnostics = cache(&mut client); assert_eq!(diagnostics.all().len(), 0, "{:#?}", diagnostics); assert!(lockfile_path.exists()); @@ -14166,7 +14195,7 @@ fn lsp_vendor_dir() { temp_dir.path().join("deno.json"), "{ \"vendor\": true, \"lock\": false }\n", ); - client.change_configuration(json!({ "deno": { + let diagnostics = client.change_configuration(json!({ "deno": { "enable": true, "config": "./deno.json", "codeLens": { @@ -14185,7 +14214,6 @@ fn lsp_vendor_dir() { "unstable": [], } })); - let diagnostics = client.read_diagnostics(); // won't be cached until a manual cache occurs assert_eq!( diagnostics @@ -14534,6 +14562,7 @@ fn lsp_deno_json_scopes_vendor_dir() { "arguments": [[], temp_dir.url().join("project1/file.ts").unwrap()], }), ); + client.read_diagnostics(); let res = client.write_request( "textDocument/definition", json!({ @@ -14584,6 +14613,7 @@ fn lsp_deno_json_scopes_vendor_dir() { "arguments": [[], temp_dir.url().join("project2/file.ts").unwrap()], }), ); + client.read_diagnostics(); let res = client.write_request( "textDocument/definition", json!({ @@ -14634,6 +14664,7 @@ fn lsp_deno_json_scopes_vendor_dir() { "arguments": [[], temp_dir.url().join("project2/project3/file.ts").unwrap()], }), ); + client.read_diagnostics(); let res = client.write_request( "textDocument/definition", json!({ @@ -14717,6 +14748,7 @@ fn lsp_deno_json_scopes_node_modules_dir() { "arguments": [[], temp_dir.url().join("project1/file.ts").unwrap()], }), ); + client.read_diagnostics(); let res = client.write_request( "textDocument/definition", json!({ @@ -14770,6 +14802,7 @@ fn lsp_deno_json_scopes_node_modules_dir() { "arguments": [[], temp_dir.url().join("project2/file.ts").unwrap()], }), ); + client.read_diagnostics(); let res = client.write_request( "textDocument/definition", json!({ @@ -14820,6 +14853,7 @@ fn lsp_deno_json_scopes_node_modules_dir() { "arguments": [[], temp_dir.url().join("project2/project3/file.ts").unwrap()], }), ); + client.read_diagnostics(); let res = client.write_request( "textDocument/definition", json!({ @@ -15799,6 +15833,7 @@ fn lsp_deno_json_workspace_vendor_dir() { "arguments": [[], temp_dir.url().join("project1/project2/file.ts").unwrap()], }), ); + client.read_diagnostics(); let res = client.write_request( "textDocument/definition", json!({ @@ -15870,6 +15905,7 @@ fn lsp_deno_json_workspace_node_modules_dir() { "arguments": [[], temp_dir.url().join("project1/project2/file.ts").unwrap()], }), ); + client.read_diagnostics(); let res = client.write_request( "textDocument/definition", json!({ @@ -17343,6 +17379,7 @@ fn compiler_options_types() { "type": 2, }], })); + client.read_diagnostics(); let diagnostics = client.did_open_file(&source); eprintln!("{:#?}", diagnostics.all()); @@ -17416,6 +17453,7 @@ fn type_reference_import_meta() { "type": 2, }], })); + client.read_diagnostics(); let diagnostics = client.did_open_file(&source); assert_eq!(diagnostics.all().len(), 0); diff --git a/tests/util/server/src/lsp.rs b/tests/util/server/src/lsp.rs index 3a7321db62..331127dab5 100644 --- a/tests/util/server/src/lsp.rs +++ b/tests/util/server/src/lsp.rs @@ -918,7 +918,10 @@ impl LspClient { self.write_notification("textDocument/didOpen", params); } - pub fn change_configuration(&mut self, config: Value) { + pub fn change_configuration( + &mut self, + config: Value, + ) -> CollectedDiagnostics { self.config = config; if self.supports_workspace_configuration { self.write_notification( @@ -932,6 +935,7 @@ impl LspClient { json!({ "settings": &self.config }), ); } + self.read_diagnostics() } pub fn handle_configuration_request(&mut self) {