mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 15:24:46 -05:00
feat(lsp): add implementations code lens (#9441)
This commit is contained in:
parent
09b79463d7
commit
e368c5d0f9
7 changed files with 286 additions and 3 deletions
|
@ -289,6 +289,8 @@ pub fn analyze_dependencies(
|
|||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub enum CodeLensSource {
|
||||
#[serde(rename = "implementations")]
|
||||
Implementations,
|
||||
#[serde(rename = "references")]
|
||||
References,
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
@ -2372,6 +2474,121 @@ mod tests {
|
|||
harness.run().await;
|
||||
}
|
||||
|
||||
#[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![
|
||||
|
|
|
@ -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 {
|
||||
|
|
21
cli/tests/lsp/code_lens_resolve_request_impl.json
Normal file
21
cli/tests/lsp/code_lens_resolve_request_impl.json
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
12
cli/tests/lsp/did_open_notification_cl_impl.json
Normal file
12
cli/tests/lsp/did_open_notification_cl_impl.json
Normal file
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,7 @@
|
|||
"initializationOptions": {
|
||||
"enable": true,
|
||||
"codeLens": {
|
||||
"implementations": true,
|
||||
"references": true
|
||||
},
|
||||
"lint": true,
|
||||
|
|
Loading…
Reference in a new issue