diff options
Diffstat (limited to 'cli/lsp/code_lens.rs')
-rw-r--r-- | cli/lsp/code_lens.rs | 284 |
1 files changed, 284 insertions, 0 deletions
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()) +} |