diff options
author | Kitson Kelly <me@kitsonkelly.com> | 2021-01-22 21:03:16 +1100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-01-22 21:03:16 +1100 |
commit | 1a9209d1e3ed297c96a698550ab833c54c02a4ee (patch) | |
tree | 21be94f78196af33dd4a59c40fbfe2e7fa744922 /cli/lsp/language_server.rs | |
parent | ffa920e4b9594f201756f9eeca542e5dfb8576d1 (diff) |
fix(lsp): handle mbc documents properly (#9151)
Co-authored-by: Ryan Dahl <ry@tinyclouds.org>
Diffstat (limited to 'cli/lsp/language_server.rs')
-rw-r--r-- | cli/lsp/language_server.rs | 600 |
1 files changed, 319 insertions, 281 deletions
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 6f7f436b9..316aabf91 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -23,33 +23,31 @@ use tokio::fs; use crate::deno_dir; use crate::import_map::ImportMap; -use crate::media_type::MediaType; use crate::tsc_config::parse_config; use crate::tsc_config::TsConfig; -use super::analysis; use super::capabilities; use super::config::Config; use super::diagnostics; use super::diagnostics::DiagnosticCollection; use super::diagnostics::DiagnosticSource; -use super::memory_cache::MemoryCache; +use super::documents::DocumentCache; use super::sources; use super::sources::Sources; use super::text; -use super::text::apply_content_changes; +use super::text::LineIndex; use super::tsc; +use super::tsc::AssetDocument; use super::tsc::TsServer; use super::utils; #[derive(Debug, Clone)] pub struct LanguageServer { - assets: Arc<Mutex<HashMap<ModuleSpecifier, Option<String>>>>, + assets: Arc<Mutex<HashMap<ModuleSpecifier, Option<AssetDocument>>>>, client: Client, ts_server: TsServer, config: Arc<Mutex<Config>>, - doc_data: Arc<Mutex<HashMap<ModuleSpecifier, DocumentData>>>, - file_cache: Arc<Mutex<MemoryCache>>, + documents: Arc<Mutex<DocumentCache>>, sources: Arc<Mutex<Sources>>, diagnostics: Arc<Mutex<DiagnosticCollection>>, maybe_config_uri: Arc<Mutex<Option<Url>>>, @@ -59,9 +57,8 @@ pub struct LanguageServer { #[derive(Debug, Clone, Default)] pub struct StateSnapshot { - pub assets: Arc<Mutex<HashMap<ModuleSpecifier, Option<String>>>>, - pub doc_data: HashMap<ModuleSpecifier, DocumentData>, - pub file_cache: Arc<Mutex<MemoryCache>>, + pub assets: Arc<Mutex<HashMap<ModuleSpecifier, Option<AssetDocument>>>>, + pub documents: Arc<Mutex<DocumentCache>>, pub sources: Arc<Mutex<Sources>>, } @@ -78,8 +75,7 @@ impl LanguageServer { client, ts_server: TsServer::new(), config: Default::default(), - doc_data: Default::default(), - file_cache: Default::default(), + documents: Default::default(), sources, diagnostics: Default::default(), maybe_config_uri: Default::default(), @@ -93,34 +89,65 @@ impl LanguageServer { config.settings.enable } + /// Searches assets, open documents and external sources for a line_index, + /// which might be performed asynchronously, hydrating in memory caches for + /// subsequent requests. pub async fn get_line_index( &self, specifier: ModuleSpecifier, - ) -> Result<Vec<u32>, AnyError> { - let line_index = if specifier.as_url().scheme() == "asset" { - let state_snapshot = self.snapshot(); - if let Some(source) = - tsc::get_asset(&specifier, &self.ts_server, &state_snapshot).await? - { - text::index_lines(&source) + ) -> Result<LineIndex, AnyError> { + if specifier.as_url().scheme() == "asset" { + let maybe_asset = + { self.assets.lock().unwrap().get(&specifier).cloned() }; + if let Some(maybe_asset) = maybe_asset { + if let Some(asset) = maybe_asset { + Ok(asset.line_index) + } else { + Err(anyhow!("asset is missing: {}", specifier)) + } } else { - return Err(anyhow!("asset source missing: {}", specifier)); + let state_snapshot = self.snapshot(); + if let Some(asset) = + tsc::get_asset(&specifier, &self.ts_server, &state_snapshot).await? + { + Ok(asset.line_index) + } else { + Err(anyhow!("asset is missing: {}", specifier)) + } } + } else if let Some(line_index) = + self.documents.lock().unwrap().line_index(&specifier) + { + Ok(line_index) + } else if let Some(line_index) = + self.sources.lock().unwrap().get_line_index(&specifier) + { + Ok(line_index) } else { - let file_cache = self.file_cache.lock().unwrap(); - if let Some(file_id) = file_cache.lookup(&specifier) { - let file_text = file_cache.get_contents(file_id)?; - text::index_lines(&file_text) + Err(anyhow!("Unable to find line index for: {}", specifier)) + } + } + + /// Only searches already cached assets and documents for a line index. If + /// the line index cannot be found, `None` is returned. + pub fn get_line_index_sync( + &self, + specifier: &ModuleSpecifier, + ) -> Option<LineIndex> { + if specifier.as_url().scheme() == "asset" { + if let Some(Some(asset)) = self.assets.lock().unwrap().get(specifier) { + Some(asset.line_index.clone()) } else { - let mut sources = self.sources.lock().unwrap(); - if let Some(line_index) = sources.get_line_index(&specifier) { - line_index - } else { - return Err(anyhow!("source for specifier not found: {}", specifier)); - } + None } - }; - Ok(line_index) + } else { + let documents = self.documents.lock().unwrap(); + if documents.contains(specifier) { + documents.line_index(specifier) + } else { + self.sources.lock().unwrap().get_line_index(specifier) + } + } } async fn prepare_diagnostics(&self) -> Result<(), AnyError> { @@ -130,6 +157,7 @@ impl LanguageServer { }; let lint = async { + let mut disturbed = false; if lint_enabled { let diagnostic_collection = self.diagnostics.lock().unwrap().clone(); let diagnostics = diagnostics::generate_lint_diagnostics( @@ -137,59 +165,50 @@ impl LanguageServer { diagnostic_collection, ) .await; + disturbed = !diagnostics.is_empty(); { let mut diagnostics_collection = self.diagnostics.lock().unwrap(); - for (file_id, version, diagnostics) in diagnostics { + for (specifier, version, diagnostics) in diagnostics { diagnostics_collection.set( - file_id, + specifier, DiagnosticSource::Lint, version, diagnostics, ); } } - self.publish_diagnostics().await? }; - - Ok::<(), AnyError>(()) + Ok::<bool, AnyError>(disturbed) }; let ts = async { + let mut disturbed = false; if enabled { - let diagnostics = { - let diagnostic_collection = self.diagnostics.lock().unwrap().clone(); - match diagnostics::generate_ts_diagnostics( - &self.ts_server, - &diagnostic_collection, - self.snapshot(), - ) - .await - { - Ok(diagnostics) => diagnostics, - Err(err) => { - error!("Error processing TypeScript diagnostics:\n{}", err); - vec![] - } - } - }; + let diagnostics_collection = self.diagnostics.lock().unwrap().clone(); + let diagnostics = diagnostics::generate_ts_diagnostics( + self.snapshot(), + diagnostics_collection, + &self.ts_server, + ) + .await?; + disturbed = !diagnostics.is_empty(); { let mut diagnostics_collection = self.diagnostics.lock().unwrap(); - for (file_id, version, diagnostics) in diagnostics { + for (specifier, version, diagnostics) in diagnostics { diagnostics_collection.set( - file_id, + specifier, DiagnosticSource::TypeScript, version, diagnostics, ); } - }; - self.publish_diagnostics().await? - } - - Ok::<(), AnyError>(()) + } + }; + Ok::<bool, AnyError>(disturbed) }; let deps = async { + let mut disturbed = false; if enabled { let diagnostics_collection = self.diagnostics.lock().unwrap().clone(); let diagnostics = diagnostics::generate_dependency_diagnostics( @@ -197,27 +216,26 @@ impl LanguageServer { diagnostics_collection, ) .await?; + disturbed = !diagnostics.is_empty(); { let mut diagnostics_collection = self.diagnostics.lock().unwrap(); - for (file_id, version, diagnostics) in diagnostics { + for (specifier, version, diagnostics) in diagnostics { diagnostics_collection.set( - file_id, + specifier, DiagnosticSource::Deno, version, diagnostics, ); } } - self.publish_diagnostics().await? }; - - Ok::<(), AnyError>(()) + Ok::<bool, AnyError>(disturbed) }; let (lint_res, ts_res, deps_res) = tokio::join!(lint, ts, deps); - lint_res?; - ts_res?; - deps_res?; + if lint_res? || ts_res? || deps_res? { + self.publish_diagnostics().await?; + } Ok(()) } @@ -230,7 +248,7 @@ impl LanguageServer { }; if let Some(diagnostic_changes) = maybe_changes { let settings = self.config.lock().unwrap().settings.clone(); - for file_id in diagnostic_changes { + for specifier in diagnostic_changes { // TODO(@kitsonk) not totally happy with the way we collect and store // different types of diagnostics and offer them up to the client, we // do need to send "empty" vectors though when a particular feature is @@ -238,7 +256,7 @@ impl LanguageServer { // diagnostics let mut diagnostics: Vec<Diagnostic> = if settings.lint { diagnostics_collection - .diagnostics_for(file_id, DiagnosticSource::Lint) + .diagnostics_for(&specifier, &DiagnosticSource::Lint) .cloned() .collect() } else { @@ -247,27 +265,17 @@ impl LanguageServer { if self.enabled() { diagnostics.extend( diagnostics_collection - .diagnostics_for(file_id, DiagnosticSource::TypeScript) + .diagnostics_for(&specifier, &DiagnosticSource::TypeScript) .cloned(), ); diagnostics.extend( diagnostics_collection - .diagnostics_for(file_id, DiagnosticSource::Deno) + .diagnostics_for(&specifier, &DiagnosticSource::Deno) .cloned(), ); } - let specifier = { - let file_cache = self.file_cache.lock().unwrap(); - file_cache.get_specifier(file_id).clone() - }; let uri = specifier.as_url().clone(); - let version = if let Some(doc_data) = - self.doc_data.lock().unwrap().get(&specifier) - { - doc_data.version - } else { - None - }; + let version = self.documents.lock().unwrap().version(&specifier); self .client .publish_diagnostics(uri, diagnostics, version) @@ -281,8 +289,7 @@ impl LanguageServer { pub fn snapshot(&self) -> StateSnapshot { StateSnapshot { assets: self.assets.clone(), - doc_data: self.doc_data.lock().unwrap().clone(), - file_cache: self.file_cache.clone(), + documents: self.documents.clone(), sources: self.sources.clone(), } } @@ -507,61 +514,48 @@ impl lspower::LanguageServer for LanguageServer { return; } let specifier = utils::normalize_url(params.text_document.uri); - let maybe_import_map = self.maybe_import_map.lock().unwrap().clone(); - if self - .doc_data + self.documents.lock().unwrap().open( + specifier.clone(), + params.text_document.version, + params.text_document.text, + ); + if let Err(err) = self + .documents .lock() .unwrap() - .insert( - specifier.clone(), - DocumentData::new( - specifier.clone(), - params.text_document.version, - ¶ms.text_document.text, - maybe_import_map, - ), - ) - .is_some() + .analyze_dependencies(&specifier, &self.maybe_import_map.lock().unwrap()) { - error!("duplicate DidOpenTextDocument: {}", specifier); + error!("{}", err); } - self - .file_cache - .lock() - .unwrap() - .set_contents(specifier, Some(params.text_document.text.into_bytes())); - // TODO(@lucacasonato): error handling - self.prepare_diagnostics().await.unwrap(); + // TODO(@kitsonk): how to better lazily do this? + if let Err(err) = self.prepare_diagnostics().await { + error!("{}", err); + } } async fn did_change(&self, params: DidChangeTextDocumentParams) { let specifier = utils::normalize_url(params.text_document.uri); - let mut content = { - let file_cache = self.file_cache.lock().unwrap(); - let file_id = file_cache.lookup(&specifier).unwrap(); - file_cache.get_contents(file_id).unwrap() - }; - apply_content_changes(&mut content, params.content_changes); - { - let mut doc_data = self.doc_data.lock().unwrap(); - let doc_data = doc_data.get_mut(&specifier).unwrap(); - let maybe_import_map = self.maybe_import_map.lock().unwrap(); - doc_data.update( - params.text_document.version, - &content, - &maybe_import_map, - ); + if let Err(err) = self.documents.lock().unwrap().change( + &specifier, + params.text_document.version, + params.content_changes, + ) { + error!("{}", err); } - - self - .file_cache + if let Err(err) = self + .documents .lock() .unwrap() - .set_contents(specifier, Some(content.into_bytes())); + .analyze_dependencies(&specifier, &self.maybe_import_map.lock().unwrap()) + { + error!("{}", err); + } - // TODO(@lucacasonato): error handling - self.prepare_diagnostics().await.unwrap(); + // TODO(@kitsonk): how to better lazily do this? + if let Err(err) = self.prepare_diagnostics().await { + error!("{}", err); + } } async fn did_close(&self, params: DidCloseTextDocumentParams) { @@ -572,12 +566,12 @@ impl lspower::LanguageServer for LanguageServer { return; } let specifier = utils::normalize_url(params.text_document.uri); - if self.doc_data.lock().unwrap().remove(&specifier).is_none() { - error!("orphaned document: {}", specifier); + self.documents.lock().unwrap().close(&specifier); + + // TODO(@kitsonk): how to better lazily do this? + if let Err(err) = self.prepare_diagnostics().await { + error!("{}", err); } - // TODO(@kitsonk) should we do garbage collection on the diagnostics? - // TODO(@lucacasonato): error handling - self.prepare_diagnostics().await.unwrap(); } async fn did_save(&self, _params: DidSaveTextDocumentParams) { @@ -673,12 +667,17 @@ impl lspower::LanguageServer for LanguageServer { params: DocumentFormattingParams, ) -> LspResult<Option<Vec<TextEdit>>> { let specifier = utils::normalize_url(params.text_document.uri.clone()); - let file_text = { - let file_cache = self.file_cache.lock().unwrap(); - let file_id = file_cache.lookup(&specifier).unwrap(); - // TODO(lucacasonato): handle error properly - file_cache.get_contents(file_id).unwrap() - }; + let file_text = self + .documents + .lock() + .unwrap() + .content(&specifier) + .map_err(|_| { + LspError::invalid_params( + "The specified file could not be found in memory.", + ) + })? + .unwrap(); let file_path = if let Ok(file_path) = params.text_document.uri.to_file_path() { @@ -723,14 +722,18 @@ impl lspower::LanguageServer for LanguageServer { let specifier = utils::normalize_url( params.text_document_position_params.text_document.uri, ); - // TODO(lucacasonato): handle error correctly - let line_index = self.get_line_index(specifier.clone()).await.unwrap(); + let line_index = + if let Some(line_index) = self.get_line_index_sync(&specifier) { + line_index + } else { + return Err(LspError::invalid_params(format!( + "An unexpected specifier ({}) was provided.", + specifier + ))); + }; let req = tsc::RequestMethod::GetQuickInfo(( specifier, - text::to_char_pos( - &line_index, - params.text_document_position_params.position, - ), + line_index.offset_tsc(params.text_document_position_params.position)?, )); // TODO(lucacasonato): handle error correctly let res = self.ts_server.request(self.snapshot(), req).await.unwrap(); @@ -738,7 +741,8 @@ impl lspower::LanguageServer for LanguageServer { let maybe_quick_info: Option<tsc::QuickInfo> = serde_json::from_value(res).unwrap(); if let Some(quick_info) = maybe_quick_info { - Ok(Some(quick_info.to_hover(&line_index))) + let hover = quick_info.to_hover(&line_index); + Ok(Some(hover)) } else { Ok(None) } @@ -754,15 +758,19 @@ impl lspower::LanguageServer for LanguageServer { let specifier = utils::normalize_url( params.text_document_position_params.text_document.uri, ); - // TODO(lucacasonato): handle error correctly - let line_index = self.get_line_index(specifier.clone()).await.unwrap(); + let line_index = + if let Some(line_index) = self.get_line_index_sync(&specifier) { + line_index + } else { + return Err(LspError::invalid_params(format!( + "An unexpected specifier ({}) was provided.", + specifier + ))); + }; let files_to_search = vec![specifier.clone()]; let req = tsc::RequestMethod::GetDocumentHighlights(( specifier, - text::to_char_pos( - &line_index, - params.text_document_position_params.position, - ), + line_index.offset_tsc(params.text_document_position_params.position)?, files_to_search, )); // TODO(lucacasonato): handle error correctly @@ -793,11 +801,18 @@ impl lspower::LanguageServer for LanguageServer { } let specifier = utils::normalize_url(params.text_document_position.text_document.uri); - // TODO(lucacasonato): handle error correctly - let line_index = self.get_line_index(specifier.clone()).await.unwrap(); + let line_index = + if let Some(line_index) = self.get_line_index_sync(&specifier) { + line_index + } else { + return Err(LspError::invalid_params(format!( + "An unexpected specifier ({}) was provided.", + specifier + ))); + }; let req = tsc::RequestMethod::GetReferences(( specifier, - text::to_char_pos(&line_index, params.text_document_position.position), + line_index.offset_tsc(params.text_document_position.position)?, )); // TODO(lucacasonato): handle error correctly let res = self.ts_server.request(self.snapshot(), req).await.unwrap(); @@ -836,14 +851,18 @@ impl lspower::LanguageServer for LanguageServer { let specifier = utils::normalize_url( params.text_document_position_params.text_document.uri, ); - // TODO(lucacasonato): handle error correctly - let line_index = self.get_line_index(specifier.clone()).await.unwrap(); + let line_index = + if let Some(line_index) = self.get_line_index_sync(&specifier) { + line_index + } else { + return Err(LspError::invalid_params(format!( + "An unexpected specifier ({}) was provided.", + specifier + ))); + }; let req = tsc::RequestMethod::GetDefinition(( specifier, - text::to_char_pos( - &line_index, - params.text_document_position_params.position, - ), + line_index.offset_tsc(params.text_document_position_params.position)?, )); // TODO(lucacasonato): handle error correctly let res = self.ts_server.request(self.snapshot(), req).await.unwrap(); @@ -872,10 +891,18 @@ impl lspower::LanguageServer for LanguageServer { let specifier = utils::normalize_url(params.text_document_position.text_document.uri); // TODO(lucacasonato): handle error correctly - let line_index = self.get_line_index(specifier.clone()).await.unwrap(); + let line_index = + if let Some(line_index) = self.get_line_index_sync(&specifier) { + line_index + } else { + return Err(LspError::invalid_params(format!( + "An unexpected specifier ({}) was provided.", + specifier + ))); + }; let req = tsc::RequestMethod::GetCompletions(( specifier, - text::to_char_pos(&line_index, params.text_document_position.position), + line_index.offset_tsc(params.text_document_position.position)?, tsc::UserPreferences { // TODO(lucacasonato): enable this. see https://github.com/denoland/deno/pull/8651 include_completions_with_insert_text: Some(false), @@ -906,20 +933,18 @@ impl lspower::LanguageServer for LanguageServer { params.text_document_position_params.text_document.uri, ); let line_index = - self - .get_line_index(specifier.clone()) - .await - .map_err(|err| { - error!("Failed to get line_index {:#?}", err); - LspError::internal_error() - })?; + if let Some(line_index) = self.get_line_index_sync(&specifier) { + line_index + } else { + return Err(LspError::invalid_params(format!( + "An unexpected specifier ({}) was provided.", + specifier + ))); + }; let req = tsc::RequestMethod::GetImplementation(( specifier, - text::to_char_pos( - &line_index, - params.text_document_position_params.position, - ), + line_index.offset_tsc(params.text_document_position_params.position)?, )); let res = self @@ -965,36 +990,36 @@ impl lspower::LanguageServer for LanguageServer { if !self.enabled() { return Ok(None); } - - let snapshot = self.snapshot(); let specifier = utils::normalize_url(params.text_document_position.text_document.uri); let line_index = - self - .get_line_index(specifier.clone()) - .await - .map_err(|err| { - error!("Failed to get line_index {:#?}", err); - LspError::internal_error() - })?; + if let Some(line_index) = self.get_line_index_sync(&specifier) { + line_index + } else { + return Err(LspError::invalid_params(format!( + "An unexpected specifier ({}) was provided.", + specifier + ))); + }; let req = tsc::RequestMethod::FindRenameLocations(( specifier, - text::to_char_pos(&line_index, params.text_document_position.position), + line_index.offset_tsc(params.text_document_position.position)?, true, true, false, )); - let res = self - .ts_server - .request(snapshot.clone(), req) - .await - .map_err(|err| { - error!("Failed to request to tsserver {:#?}", err); - LspError::invalid_request() - })?; + let res = + self + .ts_server + .request(self.snapshot(), req) + .await + .map_err(|err| { + error!("Failed to request to tsserver {:#?}", err); + LspError::invalid_request() + })?; let maybe_locations = serde_json::from_value::< Option<Vec<tsc::RenameLocation>>, @@ -1007,26 +1032,22 @@ impl lspower::LanguageServer for LanguageServer { LspError::internal_error() })?; - match maybe_locations { - Some(locations) => { - let rename_locations = tsc::RenameLocations { locations }; - let workpace_edits = rename_locations - .into_workspace_edit( - snapshot, - |s| self.get_line_index(s), - ¶ms.new_name, - ) - .await - .map_err(|err| { - error!( - "Failed to convert tsc::RenameLocations to WorkspaceEdit {:#?}", - err - ); - LspError::internal_error() - })?; - Ok(Some(workpace_edits)) - } - None => Ok(None), + if let Some(locations) = maybe_locations { + let rename_locations = tsc::RenameLocations { locations }; + let workspace_edits = rename_locations + .into_workspace_edit( + ¶ms.new_name, + |s| self.get_line_index(s), + |s| self.documents.lock().unwrap().version(&s), + ) + .await + .map_err(|err| { + error!("Failed to get workspace edits: {:#?}", err); + LspError::internal_error() + })?; + Ok(Some(workspace_edits)) + } else { + Ok(None) } } @@ -1090,12 +1111,8 @@ impl LanguageServer { error!("{}", err); LspError::internal_error() })?; - { - let file_cache = self.file_cache.lock().unwrap(); - if let Some(file_id) = file_cache.lookup(&specifier) { - let mut diagnostics_collection = self.diagnostics.lock().unwrap(); - diagnostics_collection.invalidate(&file_id); - } + if self.documents.lock().unwrap().contains(&specifier) { + self.diagnostics.lock().unwrap().invalidate(&specifier); } self.prepare_diagnostics().await.map_err(|err| { error!("{}", err); @@ -1111,28 +1128,38 @@ impl LanguageServer { let specifier = utils::normalize_url(params.text_document.uri); let url = specifier.as_url(); let contents = if url.as_str() == "deno:/status.md" { - let file_cache = self.file_cache.lock().unwrap(); + let documents = self.documents.lock().unwrap(); Some(format!( r#"# Deno Language Server Status - Documents in memory: {} "#, - file_cache.len() + documents.len() )) } else { match url.scheme() { "asset" => { - let state_snapshot = self.snapshot(); - if let Some(text) = - tsc::get_asset(&specifier, &self.ts_server, &state_snapshot) - .await - .map_err(|_| LspError::internal_error())? - { - Some(text) + let maybe_asset = + { self.assets.lock().unwrap().get(&specifier).cloned() }; + if let Some(maybe_asset) = maybe_asset { + if let Some(asset) = maybe_asset { + Some(asset.text) + } else { + None + } } else { - error!("Missing asset: {}", specifier); - None + let state_snapshot = self.snapshot(); + if let Some(asset) = + tsc::get_asset(&specifier, &self.ts_server, &state_snapshot) + .await + .map_err(|_| LspError::internal_error())? + { + Some(asset.text) + } else { + error!("Missing asset: {}", specifier); + None + } } } _ => { @@ -1150,59 +1177,6 @@ impl LanguageServer { } } -#[derive(Debug, Clone)] -pub struct DocumentData { - pub dependencies: Option<HashMap<String, analysis::Dependency>>, - pub version: Option<i32>, - specifier: ModuleSpecifier, -} - -impl DocumentData { - pub fn new( - specifier: ModuleSpecifier, - version: i32, - source: &str, - maybe_import_map: Option<ImportMap>, - ) -> Self { - let dependencies = if let Some((dependencies, _)) = - analysis::analyze_dependencies( - &specifier, - source, - &MediaType::from(&specifier), - &maybe_import_map, - ) { - Some(dependencies) - } else { - None - }; - Self { - dependencies, - version: Some(version), - specifier, - } - } - - pub fn update( - &mut self, - version: i32, - source: &str, - maybe_import_map: &Option<ImportMap>, - ) { - self.dependencies = if let Some((dependencies, _)) = - analysis::analyze_dependencies( - &self.specifier, - source, - &MediaType::from(&self.specifier), - maybe_import_map, - ) { - Some(dependencies) - } else { - None - }; - self.version = Some(version) - } -} - #[cfg(test)] mod tests { use super::*; @@ -1211,6 +1185,7 @@ mod tests { use lspower::LspService; use std::fs; use std::task::Poll; + use std::time::Instant; use tower_test::mock::Spawn; enum LspResponse { @@ -1410,6 +1385,69 @@ mod tests { ]); harness.run().await; } + + #[tokio::test] + async fn test_hover_change_mbc() { + let mut harness = LspTestHarness::new(vec![ + ("initialize_request.json", LspResponse::RequestAny), + ("initialized_notification.json", LspResponse::None), + ("did_open_notification_mbc.json", LspResponse::None), + ("did_change_notification_mbc.json", LspResponse::None), + ( + "hover_request_mbc.json", + LspResponse::Request( + 2, + json!({ + "contents": [ + { + "language": "typescript", + "value": "const b: \"😃\"", + }, + "", + ], + "range": { + "start": { + "line": 2, + "character": 13, + }, + "end": { + "line": 2, + "character": 14, + }, + } + }), + ), + ), + ( + "shutdown_request.json", + LspResponse::Request(3, json!(null)), + ), + ("exit_notification.json", LspResponse::None), + ]); + harness.run().await; + } + + #[tokio::test] + async fn test_large_doc_change() { + let mut harness = LspTestHarness::new(vec![ + ("initialize_request.json", LspResponse::RequestAny), + ("initialized_notification.json", LspResponse::None), + ("did_open_notification_large.json", LspResponse::None), + ("did_change_notification_large.json", LspResponse::None), + ( + "shutdown_request.json", + LspResponse::Request(3, json!(null)), + ), + ("exit_notification.json", LspResponse::None), + ]); + let time = Instant::now(); + harness.run().await; + assert!( + time.elapsed().as_millis() <= 10000, + "the execution time exceeded 10000ms" + ); + } + #[tokio::test] async fn test_rename() { let mut harness = LspTestHarness::new(vec![ |