diff options
author | Kitson Kelly <me@kitsonkelly.com> | 2021-06-05 07:31:44 +1000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-05 07:31:44 +1000 |
commit | e8be116ab6d06bed764ad9b6cb253d8de36ae73d (patch) | |
tree | 47f0ac3695ce7dd76fc9af1d7991c7ba685adf85 /cli/lsp | |
parent | 1abff0e333861211b5186527bc1c1371709ce3e4 (diff) |
fix(lsp): refactor, fix issues and add benchmark for code lens (#10841)
Diffstat (limited to 'cli/lsp')
-rw-r--r-- | cli/lsp/analysis.rs | 16 | ||||
-rw-r--r-- | cli/lsp/code_lens.rs | 284 | ||||
-rw-r--r-- | cli/lsp/documents.rs | 31 | ||||
-rw-r--r-- | cli/lsp/language_server.rs | 365 | ||||
-rw-r--r-- | cli/lsp/mod.rs | 1 | ||||
-rw-r--r-- | cli/lsp/sources.rs | 47 | ||||
-rw-r--r-- | cli/lsp/tsc.rs | 22 |
7 files changed, 436 insertions, 330 deletions
diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs index bd3ce799a..2235a2c83 100644 --- a/cli/lsp/analysis.rs +++ b/cli/lsp/analysis.rs @@ -15,7 +15,6 @@ use deno_core::error::anyhow; use deno_core::error::custom_error; use deno_core::error::AnyError; use deno_core::serde::Deserialize; -use deno_core::serde::Serialize; use deno_core::serde_json; use deno_core::serde_json::json; use deno_core::ModuleResolutionError; @@ -394,21 +393,6 @@ pub fn analyze_dependencies( (dependencies, maybe_type) } -#[derive(Debug, Deserialize, Serialize)] -pub enum CodeLensSource { - #[serde(rename = "implementations")] - Implementations, - #[serde(rename = "references")] - References, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CodeLensData { - pub source: CodeLensSource, - pub specifier: ModuleSpecifier, -} - fn code_as_string(code: &Option<lsp::NumberOrString>) -> String { match code { Some(lsp::NumberOrString::String(str)) => str.clone(), diff --git a/cli/lsp/code_lens.rs b/cli/lsp/code_lens.rs new file mode 100644 index 000000000..9be5bed2c --- /dev/null +++ b/cli/lsp/code_lens.rs @@ -0,0 +1,284 @@ +// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. + +use super::language_server; +use super::tsc; + +use deno_core::error::anyhow; +use deno_core::error::AnyError; +use deno_core::resolve_url; +use deno_core::serde::Deserialize; +use deno_core::serde::Serialize; +use deno_core::serde_json; +use deno_core::serde_json::json; +use deno_core::ModuleSpecifier; +use lspower::lsp; +use regex::Regex; +use std::cell::RefCell; +use std::rc::Rc; + +lazy_static::lazy_static! { + static ref ABSTRACT_MODIFIER: Regex = Regex::new(r"\babstract\b").unwrap(); + static ref EXPORT_MODIFIER: Regex = Regex::new(r"\bexport\b").unwrap(); +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum CodeLensSource { + #[serde(rename = "implementations")] + Implementations, + #[serde(rename = "references")] + References, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CodeLensData { + pub source: CodeLensSource, + pub specifier: ModuleSpecifier, +} + +async fn resolve_implementation_code_lens( + code_lens: lsp::CodeLens, + data: CodeLensData, + language_server: &mut language_server::Inner, +) -> Result<lsp::CodeLens, AnyError> { + let line_index = language_server + .get_line_index_sync(&data.specifier) + .unwrap(); + let req = tsc::RequestMethod::GetImplementation(( + data.specifier.clone(), + line_index.offset_tsc(code_lens.range.start)?, + )); + let snapshot = language_server.snapshot()?; + let maybe_implementations: Option<Vec<tsc::ImplementationLocation>> = + language_server.ts_server.request(snapshot, req).await?; + if let Some(implementations) = maybe_implementations { + let mut locations = Vec::new(); + for implementation in implementations { + let implementation_specifier = + resolve_url(&implementation.document_span.file_name)?; + let implementation_location = + implementation.to_location(&line_index, language_server); + if !(implementation_specifier == data.specifier + && implementation_location.range.start == code_lens.range.start) + { + locations.push(implementation_location); + } + } + let command = if !locations.is_empty() { + let title = if locations.len() > 1 { + format!("{} implementations", locations.len()) + } else { + "1 implementation".to_string() + }; + lsp::Command { + title, + command: "deno.showReferences".to_string(), + arguments: Some(vec![ + json!(data.specifier), + json!(code_lens.range.start), + json!(locations), + ]), + } + } else { + lsp::Command { + title: "0 implementations".to_string(), + command: "".to_string(), + arguments: None, + } + }; + Ok(lsp::CodeLens { + range: code_lens.range, + command: Some(command), + data: None, + }) + } else { + let command = Some(lsp::Command { + title: "0 implementations".to_string(), + command: "".to_string(), + arguments: None, + }); + Ok(lsp::CodeLens { + range: code_lens.range, + command, + data: None, + }) + } +} + +async fn resolve_references_code_lens( + code_lens: lsp::CodeLens, + data: CodeLensData, + language_server: &mut language_server::Inner, +) -> Result<lsp::CodeLens, AnyError> { + let line_index = language_server + .get_line_index_sync(&data.specifier) + .unwrap(); + let req = tsc::RequestMethod::GetReferences(( + data.specifier.clone(), + line_index.offset_tsc(code_lens.range.start)?, + )); + let snapshot = language_server.snapshot()?; + let maybe_references: Option<Vec<tsc::ReferenceEntry>> = + language_server.ts_server.request(snapshot, req).await?; + if let Some(references) = maybe_references { + let mut locations = Vec::new(); + for reference in references { + if reference.is_definition { + continue; + } + let reference_specifier = + resolve_url(&reference.document_span.file_name)?; + let line_index = + language_server.get_line_index(reference_specifier).await?; + locations.push(reference.to_location(&line_index, language_server)); + } + let command = if !locations.is_empty() { + let title = if locations.len() > 1 { + format!("{} references", locations.len()) + } else { + "1 reference".to_string() + }; + lsp::Command { + title, + command: "deno.showReferences".to_string(), + arguments: Some(vec![ + json!(data.specifier), + json!(code_lens.range.start), + json!(locations), + ]), + } + } else { + lsp::Command { + title: "0 references".to_string(), + command: "".to_string(), + arguments: None, + } + }; + Ok(lsp::CodeLens { + range: code_lens.range, + command: Some(command), + data: None, + }) + } else { + let command = lsp::Command { + title: "0 references".to_string(), + command: "".to_string(), + arguments: None, + }; + Ok(lsp::CodeLens { + range: code_lens.range, + command: Some(command), + data: None, + }) + } +} + +pub(crate) async fn resolve_code_lens( + code_lens: lsp::CodeLens, + language_server: &mut language_server::Inner, +) -> Result<lsp::CodeLens, AnyError> { + 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 + } + CodeLensSource::References => { + resolve_references_code_lens(code_lens, data, language_server).await + } + } +} + +/// Return tsc navigation tree code lenses. +pub(crate) async fn tsc_code_lenses( + specifier: &ModuleSpecifier, + language_server: &mut language_server::Inner, +) -> Result<Vec<lsp::CodeLens>, AnyError> { + let workspace_settings = language_server.config.get_workspace_settings(); + let line_index = language_server + .get_line_index_sync(&specifier) + .ok_or_else(|| anyhow!("Missing line index."))?; + let navigation_tree = language_server.get_navigation_tree(specifier).await?; + let code_lenses = Rc::new(RefCell::new(Vec::new())); + navigation_tree.walk(&|i, mp| { + let mut code_lenses = code_lenses.borrow_mut(); + + // TSC Implementations Code Lens + if workspace_settings.code_lens.implementations { + let source = CodeLensSource::Implementations; + match i.kind { + tsc::ScriptElementKind::InterfaceElement => { + code_lenses.push(i.to_code_lens(&line_index, specifier, &source)); + } + tsc::ScriptElementKind::ClassElement + | tsc::ScriptElementKind::MemberFunctionElement + | tsc::ScriptElementKind::MemberVariableElement + | tsc::ScriptElementKind::MemberGetAccessorElement + | tsc::ScriptElementKind::MemberSetAccessorElement => { + if ABSTRACT_MODIFIER.is_match(&i.kind_modifiers) { + code_lenses.push(i.to_code_lens(&line_index, &specifier, &source)); + } + } + _ => (), + } + } + + // TSC References Code Lens + if workspace_settings.code_lens.references { + let source = CodeLensSource::References; + if let Some(parent) = &mp { + if parent.kind == tsc::ScriptElementKind::EnumElement { + code_lenses.push(i.to_code_lens(&line_index, &specifier, &source)); + } + } + match i.kind { + tsc::ScriptElementKind::FunctionElement => { + if workspace_settings.code_lens.references_all_functions { + code_lenses.push(i.to_code_lens(&line_index, &specifier, &source)); + } + } + tsc::ScriptElementKind::ConstElement + | tsc::ScriptElementKind::LetElement + | tsc::ScriptElementKind::VariableElement => { + if EXPORT_MODIFIER.is_match(&i.kind_modifiers) { + code_lenses.push(i.to_code_lens(&line_index, &specifier, &source)); + } + } + tsc::ScriptElementKind::ClassElement => { + if i.text != "<class>" { + code_lenses.push(i.to_code_lens(&line_index, &specifier, &source)); + } + } + tsc::ScriptElementKind::InterfaceElement + | tsc::ScriptElementKind::TypeElement + | tsc::ScriptElementKind::EnumElement => { + code_lenses.push(i.to_code_lens(&line_index, &specifier, &source)); + } + tsc::ScriptElementKind::LocalFunctionElement + | tsc::ScriptElementKind::MemberGetAccessorElement + | tsc::ScriptElementKind::MemberSetAccessorElement + | tsc::ScriptElementKind::ConstructorImplementationElement + | tsc::ScriptElementKind::MemberVariableElement => { + if let Some(parent) = &mp { + if parent.spans[0].start != i.spans[0].start { + match parent.kind { + tsc::ScriptElementKind::ClassElement + | tsc::ScriptElementKind::InterfaceElement + | tsc::ScriptElementKind::TypeElement => { + code_lenses.push(i.to_code_lens( + &line_index, + &specifier, + &source, + )); + } + _ => (), + } + } + } + } + _ => (), + } + } + }); + Ok(Rc::try_unwrap(code_lenses).unwrap().into_inner()) +} diff --git a/cli/lsp/documents.rs b/cli/lsp/documents.rs index 5fbfcdf52..5e08245cb 100644 --- a/cli/lsp/documents.rs +++ b/cli/lsp/documents.rs @@ -2,6 +2,7 @@ use super::analysis; use super::text::LineIndex; +use super::tsc; use crate::media_type::MediaType; @@ -66,6 +67,7 @@ pub struct DocumentData { bytes: Option<Vec<u8>>, language_id: LanguageId, line_index: Option<LineIndex>, + maybe_navigation_tree: Option<tsc::NavigationTree>, specifier: ModuleSpecifier, dependencies: Option<HashMap<String, analysis::Dependency>>, version: Option<i32>, @@ -82,6 +84,7 @@ impl DocumentData { bytes: Some(source.as_bytes().to_owned()), language_id, line_index: Some(LineIndex::new(source)), + maybe_navigation_tree: None, specifier, dependencies: None, version: Some(version), @@ -122,6 +125,7 @@ impl DocumentData { } else { Some(LineIndex::new(&content)) }; + self.maybe_navigation_tree = None; Ok(()) } @@ -236,6 +240,14 @@ impl DocumentCache { doc.dependencies.clone() } + pub fn get_navigation_tree( + &self, + specifier: &ModuleSpecifier, + ) -> Option<tsc::NavigationTree> { + let doc = self.docs.get(specifier)?; + doc.maybe_navigation_tree.clone() + } + /// Determines if the specifier should be processed for diagnostics and other /// related language server features. pub fn is_diagnosable(&self, specifier: &ModuleSpecifier) -> bool { @@ -340,6 +352,25 @@ impl DocumentCache { } } + pub fn set_navigation_tree( + &mut self, + specifier: &ModuleSpecifier, + navigation_tree: tsc::NavigationTree, + ) -> Result<(), AnyError> { + if let Some(doc) = self.docs.get_mut(specifier) { + doc.maybe_navigation_tree = Some(navigation_tree); + Ok(()) + } else { + Err(custom_error( + "NotFound", + format!( + "The specifier (\"{}\") does not exist in the document cache.", + specifier + ), + )) + } + } + pub fn specifiers(&self) -> Vec<ModuleSpecifier> { self.docs.keys().cloned().collect() } diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index cd8298573..10bb0478e 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -15,13 +15,9 @@ use lspower::jsonrpc::Result as LspResult; use lspower::lsp::request::*; use lspower::lsp::*; use lspower::Client; -use regex::Regex; use serde_json::from_value; -use std::cell::RefCell; -use std::collections::HashMap; use std::env; use std::path::PathBuf; -use std::rc::Rc; use std::sync::atomic::Ordering; use std::sync::Arc; use tokio::fs; @@ -31,10 +27,9 @@ use super::analysis::fix_ts_import_changes; use super::analysis::ts_changes_to_edit; use super::analysis::CodeActionCollection; use super::analysis::CodeActionData; -use super::analysis::CodeLensData; -use super::analysis::CodeLensSource; use super::analysis::ResolvedDependency; use super::capabilities; +use super::code_lens; use super::completions; use super::config::Config; use super::config::ConfigSnapshot; @@ -67,11 +62,6 @@ use crate::tools::fmt::get_typescript_config; pub const REGISTRIES_PATH: &str = "registries"; const SOURCES_PATH: &str = "deps"; -lazy_static::lazy_static! { - static ref ABSTRACT_MODIFIER: Regex = Regex::new(r"\babstract\b").unwrap(); - static ref EXPORT_MODIFIER: Regex = Regex::new(r"\bexport\b").unwrap(); -} - #[derive(Debug, Clone)] pub struct LanguageServer(Arc<tokio::sync::Mutex<Inner>>); @@ -94,7 +84,7 @@ pub(crate) struct Inner { /// The LSP client that this LSP server is connected to. pub(crate) client: Client, /// Configuration information. - config: Config, + pub(crate) config: Config, diagnostics_server: diagnostics::DiagnosticsServer, /// The "in-memory" documents in the editor which can be updated and changed. documents: DocumentCache, @@ -109,8 +99,6 @@ pub(crate) struct Inner { pub(crate) maybe_import_map: Option<ImportMap>, /// The URL for the import map which is used to determine relative imports. maybe_import_map_uri: Option<Url>, - /// A map of all the cached navigation trees. - navigation_trees: HashMap<ModuleSpecifier, tsc::NavigationTree>, /// A collection of measurements which instrument that performance of the LSP. performance: Performance, /// Cached sources that are read-only. @@ -155,7 +143,6 @@ impl Inner { maybe_import_map_uri: Default::default(), module_registries, module_registries_location, - navigation_trees: Default::default(), performance, sources, ts_fixable_diagnostics: Default::default(), @@ -224,7 +211,7 @@ impl Inner { /// Only searches already cached assets and documents for a line index. If /// the line index cannot be found, `None` is returned. - fn get_line_index_sync( + pub fn get_line_index_sync( &self, specifier: &ModuleSpecifier, ) -> Option<LineIndex> { @@ -269,7 +256,7 @@ impl Inner { } } - async fn get_navigation_tree( + pub(crate) async fn get_navigation_tree( &mut self, specifier: &ModuleSpecifier, ) -> Result<tsc::NavigationTree, AnyError> { @@ -277,9 +264,19 @@ impl Inner { "get_navigation_tree", Some(json!({ "specifier": specifier })), ); - if let Some(navigation_tree) = self.navigation_trees.get(specifier) { - self.performance.measure(mark); - Ok(navigation_tree.clone()) + let maybe_navigation_tree = if specifier.scheme() == "asset" { + self + .assets + .get(specifier) + .map(|o| o.clone().map(|a| a.maybe_navigation_tree).flatten()) + .flatten() + } else if self.documents.contains_key(specifier) { + self.documents.get_navigation_tree(specifier) + } else { + self.sources.get_navigation_tree(specifier) + }; + let navigation_tree = if let Some(navigation_tree) = maybe_navigation_tree { + navigation_tree } else { let navigation_tree: tsc::NavigationTree = self .ts_server @@ -288,12 +285,23 @@ impl Inner { tsc::RequestMethod::GetNavigationTree(specifier.clone()), ) .await?; - self - .navigation_trees - .insert(specifier.clone(), navigation_tree.clone()); - self.performance.measure(mark); - Ok(navigation_tree) - } + if specifier.scheme() == "asset" { + self + .assets + .set_navigation_tree(specifier, navigation_tree.clone())?; + } else if self.documents.contains_key(specifier) { + self + .documents + .set_navigation_tree(specifier, navigation_tree.clone())?; + } else { + self + .sources + .set_navigation_tree(specifier, navigation_tree.clone())?; + } + navigation_tree + }; + self.performance.measure(mark); + Ok(navigation_tree) } pub(crate) fn snapshot(&self) -> LspResult<StateSnapshot> { @@ -674,7 +682,6 @@ impl Inner { } let specifier = self.url_map.normalize_url(¶ms.text_document.uri); self.documents.close(&specifier); - self.navigation_trees.remove(&specifier); if self.documents.is_diagnosable(&specifier) { if let Err(err) = self.diagnostics_server.update() { @@ -1090,302 +1097,36 @@ impl Inner { } let mark = self.performance.mark("code_lens", Some(¶ms)); - let line_index = self.get_line_index_sync(&specifier).unwrap(); - let navigation_tree = - self.get_navigation_tree(&specifier).await.map_err(|err| { - error!("Failed to retrieve nav tree: {}", err); - LspError::invalid_request() + let code_lenses = code_lens::tsc_code_lenses(&specifier, self) + .await + .map_err(|err| { + error!("Error getting code lenses for \"{}\": {}", specifier, err); + LspError::internal_error() })?; - - // because we have to use this as a mutable in a closure, the compiler - // can't be sure when the vector will be mutated, and so a RefCell is - // required to "protect" the vector. - let cl = Rc::new(RefCell::new(Vec::new())); - navigation_tree.walk(&|i, mp| { - let mut code_lenses = cl.borrow_mut(); - let workspace_settings = self.config.get_workspace_settings(); - - // TSC Implementations Code Lens - if workspace_settings.code_lens.implementations { - let source = CodeLensSource::Implementations; - match i.kind { - tsc::ScriptElementKind::InterfaceElement => { - code_lenses.push(i.to_code_lens(&line_index, &specifier, &source)); - } - tsc::ScriptElementKind::ClassElement - | tsc::ScriptElementKind::MemberFunctionElement - | tsc::ScriptElementKind::MemberVariableElement - | tsc::ScriptElementKind::MemberGetAccessorElement - | tsc::ScriptElementKind::MemberSetAccessorElement => { - if ABSTRACT_MODIFIER.is_match(&i.kind_modifiers) { - code_lenses.push(i.to_code_lens( - &line_index, - &specifier, - &source, - )); - } - } - _ => (), - } - } - - // TSC References Code Lens - if workspace_settings.code_lens.references { - let source = CodeLensSource::References; - if let Some(parent) = &mp { - if parent.kind == tsc::ScriptElementKind::EnumElement { - code_lenses.push(i.to_code_lens(&line_index, &specifier, &source)); - } - } - match i.kind { - tsc::ScriptElementKind::FunctionElement => { - if workspace_settings.code_lens.references_all_functions { - code_lenses.push(i.to_code_lens( - &line_index, - &specifier, - &source, - )); - } - } - tsc::ScriptElementKind::ConstElement - | tsc::ScriptElementKind::LetElement - | tsc::ScriptElementKind::VariableElement => { - if EXPORT_MODIFIER.is_match(&i.kind_modifiers) { - code_lenses.push(i.to_code_lens( - &line_index, - &specifier, - &source, - )); - } - } - tsc::ScriptElementKind::ClassElement => { - if i.text != "<class>" { - code_lenses.push(i.to_code_lens( - &line_index, - &specifier, - &source, - )); - } - } - tsc::ScriptElementKind::InterfaceElement - | tsc::ScriptElementKind::TypeElement - | tsc::ScriptElementKind::EnumElement => { - code_lenses.push(i.to_code_lens(&line_index, &specifier, &source)); - } - tsc::ScriptElementKind::LocalFunctionElement - | tsc::ScriptElementKind::MemberGetAccessorElement - | tsc::ScriptElementKind::MemberSetAccessorElement - | tsc::ScriptElementKind::ConstructorImplementationElement - | tsc::ScriptElementKind::MemberVariableElement => { - if let Some(parent) = &mp { - if parent.spans[0].start != i.spans[0].start { - match parent.kind { - tsc::ScriptElementKind::ClassElement - | tsc::ScriptElementKind::InterfaceElement - | tsc::ScriptElementKind::TypeElement => { - code_lenses.push(i.to_code_lens( - &line_index, - &specifier, - &source, - )); - } - _ => (), - } - } - } - } - _ => (), - } - } - }); - self.performance.measure(mark); - Ok(Some(Rc::try_unwrap(cl).unwrap().into_inner())) + + Ok(Some(code_lenses)) } async fn code_lens_resolve( &mut self, - params: CodeLens, + code_lens: CodeLens, ) -> LspResult<CodeLens> { - let mark = self.performance.mark("code_lens_resolve", Some(¶ms)); - if let Some(data) = params.data.clone() { - let code_lens_data: CodeLensData = serde_json::from_value(data) - .map_err(|err| LspError::invalid_params(err.to_string()))?; - let code_lens = match code_lens_data.source { - CodeLensSource::Implementations => { - let line_index = - self.get_line_index_sync(&code_lens_data.specifier).unwrap(); - let req = tsc::RequestMethod::GetImplementation(( - code_lens_data.specifier.clone(), - line_index.offset_tsc(params.range.start)?, - )); - let maybe_implementations: Option<Vec<tsc::ImplementationLocation>> = - self - .ts_server - .request(self.snapshot()?, req) - .await - .map_err(|err| { - error!("Error processing TypeScript request: {}", err); - LspError::internal_error() - })?; - if let Some(implementations) = maybe_implementations { - let mut locations = Vec::new(); - for implementation in implementations { - let implementation_specifier = resolve_url( - &implementation.document_span.file_name, - ) - .map_err(|err| { - error!("Invalid specifier returned from TypeScript: {}", err); - LspError::internal_error() - })?; - let implementation_location = - implementation.to_location(&line_index, self); - if !(implementation_specifier == code_lens_data.specifier - && implementation_location.range.start == params.range.start) - { - locations.push(implementation_location); - } - } - let command = if !locations.is_empty() { - let title = if locations.len() > 1 { - format!("{} implementations", locations.len()) - } else { - "1 implementation".to_string() - }; - let url = self - .url_map - .normalize_specifier(&code_lens_data.specifier) - .map_err(|err| { - error!("{}", err); - LspError::internal_error() - })?; - Command { - title, - command: "deno.showReferences".to_string(), - arguments: Some(vec![ - serde_json::to_value(url).unwrap(), - serde_json::to_value(params.range.start).unwrap(), - serde_json::to_value(locations).unwrap(), - ]), - } - } else { - Command { - title: "0 implementations".to_string(), - command: "".to_string(), - arguments: None, - } - }; - CodeLens { - range: params.range, - command: Some(command), - data: None, - } - } else { - let command = Command { - title: "0 implementations".to_string(), - command: "".to_string(), - arguments: None, - }; - CodeLens { - range: params.range, - command: Some(command), - data: None, - } - } - } - CodeLensSource::References => { - let line_index = - self.get_line_index_sync(&code_lens_data.specifier).unwrap(); - let req = tsc::RequestMethod::GetReferences(( - code_lens_data.specifier.clone(), - line_index.offset_tsc(params.range.start)?, - )); - let maybe_references: Option<Vec<tsc::ReferenceEntry>> = self - .ts_server - .request(self.snapshot()?, req) - .await - .map_err(|err| { - error!("Error processing TypeScript request: {}", err); - LspError::internal_error() - })?; - if let Some(references) = maybe_references { - let mut locations = Vec::new(); - for reference in references { - if reference.is_definition { - continue; - } - let reference_specifier = resolve_url( - &reference.document_span.file_name, - ) - .map_err(|err| { - error!("Invalid specifier returned from TypeScript: {}", err); - LspError::internal_error() - })?; - let line_index = self - .get_line_index(reference_specifier) - .await - .map_err(|err| { - error!("Unable to get line index: {}", err); - LspError::internal_error() - })?; - locations.push(reference.to_location(&line_index, self)); - } - let command = if !locations.is_empty() { - let title = if locations.len() > 1 { - format!("{} references", locations.len()) - } else { - "1 reference".to_string() - }; - let url = self - .url_map - .normalize_specifier(&code_lens_data.specifier) - .map_err(|err| { - error!("{}", err); - LspError::internal_error() - })?; - Command { - title, - command: "deno.showReferences".to_string(), - arguments: Some(vec![ - serde_json::to_value(url).unwrap(), - serde_json::to_value(params.range.start).unwrap(), - serde_json::to_value(locations).unwrap(), - ]), - } - } else { - Command { - title: "0 references".to_string(), - command: "".to_string(), - arguments: None, - } - }; - CodeLens { - range: params.range, - command: Some(command), - data: None, - } - } else { - let command = Command { - title: "0 references".to_string(), - command: "".to_string(), - arguments: None, - }; - CodeLens { - range: params.range, - command: Some(command), - data: None, - } - } - } - }; - self.performance.measure(mark); - Ok(code_lens) + let mark = self.performance.mark("code_lens_resolve", Some(&code_lens)); + let result = if code_lens.data.is_some() { + code_lens::resolve_code_lens(code_lens, self) + .await + .map_err(|err| { + error!("Error resolving code lens: {}", err); + LspError::internal_error() + }) } else { - self.performance.measure(mark); Err(LspError::invalid_params( "Code lens is missing the \"data\" property.", )) - } + }; + self.performance.measure(mark); + result } async fn document_highlight( diff --git a/cli/lsp/mod.rs b/cli/lsp/mod.rs index 488507a5b..367257911 100644 --- a/cli/lsp/mod.rs +++ b/cli/lsp/mod.rs @@ -6,6 +6,7 @@ use lspower::Server; mod analysis; mod capabilities; +mod code_lens; mod completions; mod config; mod diagnostics; diff --git a/cli/lsp/sources.rs b/cli/lsp/sources.rs index c5586a440..74e1a6a60 100644 --- a/cli/lsp/sources.rs +++ b/cli/lsp/sources.rs @@ -2,6 +2,7 @@ use super::analysis; use super::text::LineIndex; +use super::tsc; use crate::file_fetcher::get_source_from_bytes; use crate::file_fetcher::map_content_type; @@ -15,6 +16,7 @@ use crate::program_state::ProgramState; use crate::specifier_handler::FetchHandler; use crate::text_encoding; +use deno_core::error::anyhow; use deno_core::error::AnyError; use deno_core::serde_json; use deno_core::ModuleSpecifier; @@ -26,6 +28,7 @@ use std::path::PathBuf; use std::sync::Arc; use std::sync::Mutex; use std::time::SystemTime; +use tsc::NavigationTree; pub async fn cache( specifier: &ModuleSpecifier, @@ -104,6 +107,7 @@ struct Metadata { dependencies: Option<HashMap<String, analysis::Dependency>>, length_utf16: usize, line_index: LineIndex, + maybe_navigation_tree: Option<tsc::NavigationTree>, maybe_types: Option<analysis::ResolvedDependency>, maybe_warning: Option<String>, media_type: MediaType, @@ -139,6 +143,7 @@ impl Metadata { dependencies, length_utf16: source.encode_utf16().count(), line_index, + maybe_navigation_tree: None, maybe_types, maybe_warning, media_type: media_type.to_owned(), @@ -197,6 +202,13 @@ impl Sources { self.0.lock().unwrap().get_media_type(specifier) } + pub fn get_navigation_tree( + &self, + specifier: &ModuleSpecifier, + ) -> Option<tsc::NavigationTree> { + self.0.lock().unwrap().get_navigation_tree(specifier) + } + pub fn get_script_version( &self, specifier: &ModuleSpecifier, @@ -223,6 +235,18 @@ impl Sources { pub fn specifiers(&self) -> Vec<ModuleSpecifier> { self.0.lock().unwrap().metadata.keys().cloned().collect() } + + pub fn set_navigation_tree( + &self, + specifier: &ModuleSpecifier, + navigation_tree: tsc::NavigationTree, + ) -> Result<(), AnyError> { + self + .0 + .lock() + .unwrap() + .set_navigation_tree(specifier, navigation_tree) + } } impl Inner { @@ -343,6 +367,16 @@ impl Inner { Some(metadata) } + fn get_navigation_tree( + &mut self, + specifier: &ModuleSpecifier, + ) -> Option<tsc::NavigationTree> { + let specifier = + resolve_specifier(specifier, &mut self.redirects, &self.http_cache)?; + let metadata = self.get_metadata(&specifier)?; + metadata.maybe_navigation_tree + } + fn get_path(&mut self, specifier: &ModuleSpecifier) -> Option<PathBuf> { if specifier.scheme() == "file" { specifier.to_file_path().ok() @@ -461,6 +495,19 @@ impl Inner { } } } + + fn set_navigation_tree( + &mut self, + specifier: &ModuleSpecifier, + navigation_tree: NavigationTree, + ) -> Result<(), AnyError> { + let mut metadata = self + .metadata + .get_mut(specifier) + .ok_or_else(|| anyhow!("Specifier not found {}"))?; + metadata.maybe_navigation_tree = Some(navigation_tree); + Ok(()) + } } #[cfg(test)] diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index 6a476054f..834ac0a0c 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -1,8 +1,8 @@ // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. -use super::analysis::CodeLensSource; use super::analysis::ResolvedDependency; use super::analysis::ResolvedDependencyErr; +use super::code_lens; use super::config; use super::language_server; use super::language_server::StateSnapshot; @@ -103,6 +103,7 @@ pub struct AssetDocument { pub text: String, pub length: usize, pub line_index: LineIndex, + pub maybe_navigation_tree: Option<NavigationTree>, } impl AssetDocument { @@ -112,6 +113,7 @@ impl AssetDocument { text: text.to_string(), length: text.encode_utf16().count(), line_index: LineIndex::new(text), + maybe_navigation_tree: None, } } } @@ -150,6 +152,22 @@ impl Assets { ) -> Option<Option<AssetDocument>> { self.0.insert(k, v) } + + pub fn set_navigation_tree( + &mut self, + specifier: &ModuleSpecifier, + navigation_tree: NavigationTree, + ) -> Result<(), AnyError> { + let maybe_doc = self + .0 + .get_mut(specifier) + .ok_or_else(|| anyhow!("Missing asset."))?; + let doc = maybe_doc + .as_mut() + .ok_or_else(|| anyhow!("Cannot get doc mutable"))?; + doc.maybe_navigation_tree = Some(navigation_tree); + Ok(()) + } } /// Optionally returns an internal asset, first checking for any static assets @@ -610,7 +628,7 @@ impl NavigationTree { &self, line_index: &LineIndex, specifier: &ModuleSpecifier, - source: &CodeLensSource, + source: &code_lens::CodeLensSource, ) -> lsp::CodeLens { let range = if let Some(name_span) = &self.name_span { name_span.to_range(line_index) |