summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKitson Kelly <me@kitsonkelly.com>2021-02-08 21:45:10 +1100
committerGitHub <noreply@github.com>2021-02-08 21:45:10 +1100
commite368c5d0f9d69e69438cb0a8a8deb49d7b02901a (patch)
treeb4375b56374f3aad19089e0f1016b1b892dc972c
parent09b79463d71f3b144a0cfd878108a012c87464ca (diff)
feat(lsp): add implementations code lens (#9441)
-rw-r--r--cli/lsp/analysis.rs2
-rw-r--r--cli/lsp/config.rs15
-rw-r--r--cli/lsp/language_server.rs217
-rw-r--r--cli/lsp/tsc.rs21
-rw-r--r--cli/tests/lsp/code_lens_resolve_request_impl.json21
-rw-r--r--cli/tests/lsp/did_open_notification_cl_impl.json12
-rw-r--r--cli/tests/lsp/initialize_request.json1
7 files changed, 286 insertions, 3 deletions
diff --git a/cli/lsp/analysis.rs b/cli/lsp/analysis.rs
index 7b0f159bb..0c3112ca8 100644
--- a/cli/lsp/analysis.rs
+++ b/cli/lsp/analysis.rs
@@ -289,6 +289,8 @@ pub fn analyze_dependencies(
#[derive(Debug, Deserialize, Serialize)]
pub enum CodeLensSource {
+ #[serde(rename = "implementations")]
+ Implementations,
#[serde(rename = "references")]
References,
}
diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs
index 47285a01e..cb814d0fd 100644
--- a/cli/lsp/config.rs
+++ b/cli/lsp/config.rs
@@ -18,7 +18,10 @@ pub struct ClientCapabilities {
#[derive(Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CodeLensSettings {
- /// Flag for providing reference code lens.
+ /// Flag for providing implementation code lenses.
+ #[serde(default)]
+ pub implementations: bool,
+ /// Flag for providing reference code lenses.
#[serde(default)]
pub references: bool,
/// Flag for providing reference code lens on all functions. For this to have
@@ -47,7 +50,15 @@ impl WorkspaceSettings {
pub fn enabled_code_lens(&self) -> bool {
if let Some(code_lens) = &self.code_lens {
// This should contain all the "top level" code lens references
- code_lens.references
+ code_lens.implementations || code_lens.references
+ } else {
+ false
+ }
+ }
+
+ pub fn enabled_code_lens_implementations(&self) -> bool {
+ if let Some(code_lens) = &self.code_lens {
+ code_lens.implementations
} else {
false
}
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index e22934dc3..aa5286609 100644
--- a/cli/lsp/language_server.rs
+++ b/cli/lsp/language_server.rs
@@ -52,6 +52,7 @@ use super::tsc::TsServer;
use super::utils;
lazy_static! {
+ static ref ABSTRACT_MODIFIER: Regex = Regex::new(r"\babstract\b").unwrap();
static ref EXPORT_MODIFIER: Regex = Regex::new(r"\bexport\b").unwrap();
}
@@ -1037,6 +1038,30 @@ impl Inner {
navigation_tree.walk(&|i, mp| {
let mut code_lenses = cl.borrow_mut();
+ // TSC Implementations Code Lens
+ if self.config.settings.enabled_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 self.config.settings.enabled_code_lens_references() {
let source = CodeLensSource::References;
@@ -1124,6 +1149,83 @@ impl Inner {
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 res =
+ self.ts_server.request(self.snapshot(), req).await.map_err(
+ |err| {
+ error!("Error processing TypeScript request: {}", err);
+ LspError::internal_error()
+ },
+ )?;
+ let maybe_implementations: Option<Vec<tsc::ImplementationLocation>> =
+ serde_json::from_value(res).map_err(|err| {
+ error!("Error deserializing response: {}", err);
+ LspError::internal_error()
+ })?;
+ if let Some(implementations) = maybe_implementations {
+ let mut locations = Vec::new();
+ for implementation in implementations {
+ let implementation_specifier = ModuleSpecifier::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);
+ 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()
+ };
+ Command {
+ title,
+ command: "deno.showReferences".to_string(),
+ arguments: Some(vec![
+ serde_json::to_value(code_lens_data.specifier).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();
@@ -2373,6 +2475,121 @@ mod tests {
}
#[tokio::test]
+ async fn test_code_lens_impl_request() {
+ let mut harness = LspTestHarness::new(vec![
+ ("initialize_request.json", LspResponse::RequestAny),
+ ("initialized_notification.json", LspResponse::None),
+ ("did_open_notification_cl_impl.json", LspResponse::None),
+ (
+ "code_lens_request.json",
+ LspResponse::Request(
+ 2,
+ json!([
+ {
+ "range": {
+ "start": {
+ "line": 0,
+ "character": 10,
+ },
+ "end": {
+ "line": 0,
+ "character": 11,
+ }
+ },
+ "data": {
+ "specifier": "file:///a/file.ts",
+ "source": "implementations",
+ },
+ },
+ {
+ "range": {
+ "start": {
+ "line": 0,
+ "character": 10,
+ },
+ "end": {
+ "line": 0,
+ "character": 11,
+ }
+ },
+ "data": {
+ "specifier": "file:///a/file.ts",
+ "source": "references",
+ },
+ },
+ {
+ "range": {
+ "start": {
+ "line": 4,
+ "character": 6,
+ },
+ "end": {
+ "line": 4,
+ "character": 7,
+ }
+ },
+ "data": {
+ "specifier": "file:///a/file.ts",
+ "source": "references",
+ },
+ },
+ ]),
+ ),
+ ),
+ (
+ "code_lens_resolve_request_impl.json",
+ LspResponse::Request(
+ 4,
+ json!({
+ "range": {
+ "start": {
+ "line": 0,
+ "character": 10,
+ },
+ "end": {
+ "line": 0,
+ "character": 11,
+ }
+ },
+ "command": {
+ "title": "1 implementation",
+ "command": "deno.showReferences",
+ "arguments": [
+ "file:///a/file.ts",
+ {
+ "line": 0,
+ "character": 10,
+ },
+ [
+ {
+ "uri": "file:///a/file.ts",
+ "range": {
+ "start": {
+ "line": 4,
+ "character": 6,
+ },
+ "end": {
+ "line": 4,
+ "character": 7,
+ }
+ }
+ }
+ ],
+ ]
+ }
+ }),
+ ),
+ ),
+ (
+ "shutdown_request.json",
+ LspResponse::Request(3, json!(null)),
+ ),
+ ("exit_notification.json", LspResponse::None),
+ ]);
+ harness.run().await;
+ }
+
+ #[tokio::test]
async fn test_code_actions() {
let mut harness = LspTestHarness::new(vec![
("initialize_request.json", LspResponse::RequestAny),
diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs
index 67286e288..6614ce8e6 100644
--- a/cli/lsp/tsc.rs
+++ b/cli/lsp/tsc.rs
@@ -497,8 +497,16 @@ impl NavigationTree {
specifier: &ModuleSpecifier,
source: &CodeLensSource,
) -> lsp::CodeLens {
+ let range = if let Some(name_span) = &self.name_span {
+ name_span.to_range(line_index)
+ } else if !self.spans.is_empty() {
+ let span = &self.spans[0];
+ span.to_range(line_index)
+ } else {
+ lsp::Range::default()
+ };
lsp::CodeLens {
- range: self.name_span.clone().unwrap().to_range(line_index),
+ range,
command: None,
data: Some(json!({
"specifier": specifier,
@@ -542,6 +550,17 @@ pub struct ImplementationLocation {
display_parts: Vec<SymbolDisplayPart>,
}
+impl ImplementationLocation {
+ pub fn to_location(&self, line_index: &LineIndex) -> lsp::Location {
+ let uri =
+ utils::normalize_file_name(&self.document_span.file_name).unwrap();
+ lsp::Location {
+ uri,
+ range: self.document_span.text_span.to_range(line_index),
+ }
+ }
+}
+
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RenameLocation {
diff --git a/cli/tests/lsp/code_lens_resolve_request_impl.json b/cli/tests/lsp/code_lens_resolve_request_impl.json
new file mode 100644
index 000000000..e44d19675
--- /dev/null
+++ b/cli/tests/lsp/code_lens_resolve_request_impl.json
@@ -0,0 +1,21 @@
+{
+ "jsonrpc": "2.0",
+ "id": 4,
+ "method": "codeLens/resolve",
+ "params": {
+ "range": {
+ "start": {
+ "line": 0,
+ "character": 10
+ },
+ "end": {
+ "line": 0,
+ "character": 11
+ }
+ },
+ "data": {
+ "specifier": "file:///a/file.ts",
+ "source": "implementations"
+ }
+ }
+}
diff --git a/cli/tests/lsp/did_open_notification_cl_impl.json b/cli/tests/lsp/did_open_notification_cl_impl.json
new file mode 100644
index 000000000..eabcd7ccc
--- /dev/null
+++ b/cli/tests/lsp/did_open_notification_cl_impl.json
@@ -0,0 +1,12 @@
+{
+ "jsonrpc": "2.0",
+ "method": "textDocument/didOpen",
+ "params": {
+ "textDocument": {
+ "uri": "file:///a/file.ts",
+ "languageId": "typescript",
+ "version": 1,
+ "text": "interface A {\n b(): void;\n}\n\nclass B implements A {\n b() {\n console.log(\"b\");\n }\n}\n"
+ }
+ }
+}
diff --git a/cli/tests/lsp/initialize_request.json b/cli/tests/lsp/initialize_request.json
index eaea00a18..21e9e3b4f 100644
--- a/cli/tests/lsp/initialize_request.json
+++ b/cli/tests/lsp/initialize_request.json
@@ -12,6 +12,7 @@
"initializationOptions": {
"enable": true,
"codeLens": {
+ "implementations": true,
"references": true
},
"lint": true,