mirror of
https://github.com/denoland/deno.git
synced 2024-11-25 15:29:32 -05:00
fix(lsp): refactor, fix issues and add benchmark for code lens (#10841)
This commit is contained in:
parent
1abff0e333
commit
e8be116ab6
12 changed files with 780 additions and 331 deletions
56
cli/bench/fixtures/code_lens.ts
Normal file
56
cli/bench/fixtures/code_lens.ts
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
interface A {
|
||||||
|
a: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface B {
|
||||||
|
b: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface C {
|
||||||
|
c: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface D {
|
||||||
|
d: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface E {
|
||||||
|
e: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface F {
|
||||||
|
f: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface G {
|
||||||
|
g: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface H {
|
||||||
|
h: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AB implements A, B {
|
||||||
|
a = "a";
|
||||||
|
b = "b";
|
||||||
|
}
|
||||||
|
|
||||||
|
class CD implements C, D {
|
||||||
|
c = "c";
|
||||||
|
d = "d";
|
||||||
|
}
|
||||||
|
|
||||||
|
class EF implements E, F {
|
||||||
|
e = "e";
|
||||||
|
f = "f";
|
||||||
|
}
|
||||||
|
|
||||||
|
class GH implements G, H {
|
||||||
|
g = "g";
|
||||||
|
h = "h";
|
||||||
|
}
|
||||||
|
|
||||||
|
new AB().a;
|
||||||
|
new CD().c;
|
||||||
|
new EF().e;
|
||||||
|
new GH().g;
|
|
@ -13,6 +13,7 @@ use std::time::Duration;
|
||||||
use test_util::lsp::LspClient;
|
use test_util::lsp::LspClient;
|
||||||
use test_util::lsp::LspResponseError;
|
use test_util::lsp::LspResponseError;
|
||||||
|
|
||||||
|
static FIXTURE_CODE_LENS_TS: &str = include_str!("fixtures/code_lens.ts");
|
||||||
static FIXTURE_DB_TS: &str = include_str!("fixtures/db.ts");
|
static FIXTURE_DB_TS: &str = include_str!("fixtures/db.ts");
|
||||||
static FIXTURE_DB_MESSAGES: &[u8] = include_bytes!("fixtures/db_messages.json");
|
static FIXTURE_DB_MESSAGES: &[u8] = include_bytes!("fixtures/db_messages.json");
|
||||||
static FIXTURE_INIT_JSON: &[u8] =
|
static FIXTURE_INIT_JSON: &[u8] =
|
||||||
|
@ -123,6 +124,70 @@ fn bench_big_file_edits(deno_exe: &Path) -> Result<Duration, AnyError> {
|
||||||
Ok(client.duration())
|
Ok(client.duration())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bench_code_lens(deno_exe: &Path) -> Result<Duration, AnyError> {
|
||||||
|
let mut client = LspClient::new(deno_exe)?;
|
||||||
|
|
||||||
|
let params: Value = serde_json::from_slice(FIXTURE_INIT_JSON)?;
|
||||||
|
let (_, maybe_err) =
|
||||||
|
client.write_request::<_, _, Value>("initialize", params)?;
|
||||||
|
assert!(maybe_err.is_none());
|
||||||
|
client.write_notification("initialized", json!({}))?;
|
||||||
|
|
||||||
|
client.write_notification(
|
||||||
|
"textDocument/didOpen",
|
||||||
|
json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "file:///fixtures/code_lens.ts",
|
||||||
|
"languageId": "typescript",
|
||||||
|
"version": 1,
|
||||||
|
"text": FIXTURE_CODE_LENS_TS
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let (id, method, _): (u64, String, Option<Value>) = client.read_request()?;
|
||||||
|
assert_eq!(method, "workspace/configuration");
|
||||||
|
|
||||||
|
client.write_response(
|
||||||
|
id,
|
||||||
|
json!({
|
||||||
|
"enable": true
|
||||||
|
}),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let (method, _): (String, Option<Value>) = client.read_notification()?;
|
||||||
|
assert_eq!(method, "textDocument/publishDiagnostics");
|
||||||
|
let (method, _): (String, Option<Value>) = client.read_notification()?;
|
||||||
|
assert_eq!(method, "textDocument/publishDiagnostics");
|
||||||
|
let (method, _): (String, Option<Value>) = client.read_notification()?;
|
||||||
|
assert_eq!(method, "textDocument/publishDiagnostics");
|
||||||
|
|
||||||
|
let (maybe_res, maybe_err) = client
|
||||||
|
.write_request::<_, _, Vec<lsp::CodeLens>>(
|
||||||
|
"textDocument/codeLens",
|
||||||
|
json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "file:///fixtures/code_lens.ts"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(maybe_err.is_none());
|
||||||
|
assert!(maybe_res.is_some());
|
||||||
|
let res = maybe_res.unwrap();
|
||||||
|
assert!(!res.is_empty());
|
||||||
|
|
||||||
|
for code_lens in res {
|
||||||
|
let (maybe_res, maybe_err) = client
|
||||||
|
.write_request::<_, _, lsp::CodeLens>("codeLens/resolve", code_lens)
|
||||||
|
.unwrap();
|
||||||
|
assert!(maybe_err.is_none());
|
||||||
|
assert!(maybe_res.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(client.duration())
|
||||||
|
}
|
||||||
|
|
||||||
fn bench_find_replace(deno_exe: &Path) -> Result<Duration, AnyError> {
|
fn bench_find_replace(deno_exe: &Path) -> Result<Duration, AnyError> {
|
||||||
let mut client = LspClient::new(deno_exe)?;
|
let mut client = LspClient::new(deno_exe)?;
|
||||||
|
|
||||||
|
@ -304,6 +369,16 @@ pub(crate) fn benchmarks(
|
||||||
println!(" ({} runs, mean: {}ms)", times.len(), mean);
|
println!(" ({} runs, mean: {}ms)", times.len(), mean);
|
||||||
exec_times.insert("find_replace".to_string(), mean);
|
exec_times.insert("find_replace".to_string(), mean);
|
||||||
|
|
||||||
|
println!(" - Code Lens");
|
||||||
|
let mut times = Vec::new();
|
||||||
|
for _ in 0..10 {
|
||||||
|
times.push(bench_code_lens(deno_exe)?);
|
||||||
|
}
|
||||||
|
let mean =
|
||||||
|
(times.iter().sum::<Duration>() / times.len() as u32).as_millis() as u64;
|
||||||
|
println!(" ({} runs, mean: {}ms)", times.len(), mean);
|
||||||
|
exec_times.insert("code_lens".to_string(), mean);
|
||||||
|
|
||||||
println!("<- End benchmarking lsp");
|
println!("<- End benchmarking lsp");
|
||||||
|
|
||||||
Ok(exec_times)
|
Ok(exec_times)
|
||||||
|
|
|
@ -15,7 +15,6 @@ use deno_core::error::anyhow;
|
||||||
use deno_core::error::custom_error;
|
use deno_core::error::custom_error;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::serde::Deserialize;
|
use deno_core::serde::Deserialize;
|
||||||
use deno_core::serde::Serialize;
|
|
||||||
use deno_core::serde_json;
|
use deno_core::serde_json;
|
||||||
use deno_core::serde_json::json;
|
use deno_core::serde_json::json;
|
||||||
use deno_core::ModuleResolutionError;
|
use deno_core::ModuleResolutionError;
|
||||||
|
@ -394,21 +393,6 @@ pub fn analyze_dependencies(
|
||||||
(dependencies, maybe_type)
|
(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 {
|
fn code_as_string(code: &Option<lsp::NumberOrString>) -> String {
|
||||||
match code {
|
match code {
|
||||||
Some(lsp::NumberOrString::String(str)) => str.clone(),
|
Some(lsp::NumberOrString::String(str)) => str.clone(),
|
||||||
|
|
284
cli/lsp/code_lens.rs
Normal file
284
cli/lsp/code_lens.rs
Normal file
|
@ -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())
|
||||||
|
}
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use super::analysis;
|
use super::analysis;
|
||||||
use super::text::LineIndex;
|
use super::text::LineIndex;
|
||||||
|
use super::tsc;
|
||||||
|
|
||||||
use crate::media_type::MediaType;
|
use crate::media_type::MediaType;
|
||||||
|
|
||||||
|
@ -66,6 +67,7 @@ pub struct DocumentData {
|
||||||
bytes: Option<Vec<u8>>,
|
bytes: Option<Vec<u8>>,
|
||||||
language_id: LanguageId,
|
language_id: LanguageId,
|
||||||
line_index: Option<LineIndex>,
|
line_index: Option<LineIndex>,
|
||||||
|
maybe_navigation_tree: Option<tsc::NavigationTree>,
|
||||||
specifier: ModuleSpecifier,
|
specifier: ModuleSpecifier,
|
||||||
dependencies: Option<HashMap<String, analysis::Dependency>>,
|
dependencies: Option<HashMap<String, analysis::Dependency>>,
|
||||||
version: Option<i32>,
|
version: Option<i32>,
|
||||||
|
@ -82,6 +84,7 @@ impl DocumentData {
|
||||||
bytes: Some(source.as_bytes().to_owned()),
|
bytes: Some(source.as_bytes().to_owned()),
|
||||||
language_id,
|
language_id,
|
||||||
line_index: Some(LineIndex::new(source)),
|
line_index: Some(LineIndex::new(source)),
|
||||||
|
maybe_navigation_tree: None,
|
||||||
specifier,
|
specifier,
|
||||||
dependencies: None,
|
dependencies: None,
|
||||||
version: Some(version),
|
version: Some(version),
|
||||||
|
@ -122,6 +125,7 @@ impl DocumentData {
|
||||||
} else {
|
} else {
|
||||||
Some(LineIndex::new(&content))
|
Some(LineIndex::new(&content))
|
||||||
};
|
};
|
||||||
|
self.maybe_navigation_tree = None;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,6 +240,14 @@ impl DocumentCache {
|
||||||
doc.dependencies.clone()
|
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
|
/// Determines if the specifier should be processed for diagnostics and other
|
||||||
/// related language server features.
|
/// related language server features.
|
||||||
pub fn is_diagnosable(&self, specifier: &ModuleSpecifier) -> bool {
|
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> {
|
pub fn specifiers(&self) -> Vec<ModuleSpecifier> {
|
||||||
self.docs.keys().cloned().collect()
|
self.docs.keys().cloned().collect()
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,13 +15,9 @@ use lspower::jsonrpc::Result as LspResult;
|
||||||
use lspower::lsp::request::*;
|
use lspower::lsp::request::*;
|
||||||
use lspower::lsp::*;
|
use lspower::lsp::*;
|
||||||
use lspower::Client;
|
use lspower::Client;
|
||||||
use regex::Regex;
|
|
||||||
use serde_json::from_value;
|
use serde_json::from_value;
|
||||||
use std::cell::RefCell;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::rc::Rc;
|
|
||||||
use std::sync::atomic::Ordering;
|
use std::sync::atomic::Ordering;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::fs;
|
use tokio::fs;
|
||||||
|
@ -31,10 +27,9 @@ use super::analysis::fix_ts_import_changes;
|
||||||
use super::analysis::ts_changes_to_edit;
|
use super::analysis::ts_changes_to_edit;
|
||||||
use super::analysis::CodeActionCollection;
|
use super::analysis::CodeActionCollection;
|
||||||
use super::analysis::CodeActionData;
|
use super::analysis::CodeActionData;
|
||||||
use super::analysis::CodeLensData;
|
|
||||||
use super::analysis::CodeLensSource;
|
|
||||||
use super::analysis::ResolvedDependency;
|
use super::analysis::ResolvedDependency;
|
||||||
use super::capabilities;
|
use super::capabilities;
|
||||||
|
use super::code_lens;
|
||||||
use super::completions;
|
use super::completions;
|
||||||
use super::config::Config;
|
use super::config::Config;
|
||||||
use super::config::ConfigSnapshot;
|
use super::config::ConfigSnapshot;
|
||||||
|
@ -67,11 +62,6 @@ use crate::tools::fmt::get_typescript_config;
|
||||||
pub const REGISTRIES_PATH: &str = "registries";
|
pub const REGISTRIES_PATH: &str = "registries";
|
||||||
const SOURCES_PATH: &str = "deps";
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LanguageServer(Arc<tokio::sync::Mutex<Inner>>);
|
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.
|
/// The LSP client that this LSP server is connected to.
|
||||||
pub(crate) client: Client,
|
pub(crate) client: Client,
|
||||||
/// Configuration information.
|
/// Configuration information.
|
||||||
config: Config,
|
pub(crate) config: Config,
|
||||||
diagnostics_server: diagnostics::DiagnosticsServer,
|
diagnostics_server: diagnostics::DiagnosticsServer,
|
||||||
/// The "in-memory" documents in the editor which can be updated and changed.
|
/// The "in-memory" documents in the editor which can be updated and changed.
|
||||||
documents: DocumentCache,
|
documents: DocumentCache,
|
||||||
|
@ -109,8 +99,6 @@ pub(crate) struct Inner {
|
||||||
pub(crate) maybe_import_map: Option<ImportMap>,
|
pub(crate) maybe_import_map: Option<ImportMap>,
|
||||||
/// The URL for the import map which is used to determine relative imports.
|
/// The URL for the import map which is used to determine relative imports.
|
||||||
maybe_import_map_uri: Option<Url>,
|
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.
|
/// A collection of measurements which instrument that performance of the LSP.
|
||||||
performance: Performance,
|
performance: Performance,
|
||||||
/// Cached sources that are read-only.
|
/// Cached sources that are read-only.
|
||||||
|
@ -155,7 +143,6 @@ impl Inner {
|
||||||
maybe_import_map_uri: Default::default(),
|
maybe_import_map_uri: Default::default(),
|
||||||
module_registries,
|
module_registries,
|
||||||
module_registries_location,
|
module_registries_location,
|
||||||
navigation_trees: Default::default(),
|
|
||||||
performance,
|
performance,
|
||||||
sources,
|
sources,
|
||||||
ts_fixable_diagnostics: Default::default(),
|
ts_fixable_diagnostics: Default::default(),
|
||||||
|
@ -224,7 +211,7 @@ impl Inner {
|
||||||
|
|
||||||
/// Only searches already cached assets and documents for a line index. If
|
/// Only searches already cached assets and documents for a line index. If
|
||||||
/// the line index cannot be found, `None` is returned.
|
/// the line index cannot be found, `None` is returned.
|
||||||
fn get_line_index_sync(
|
pub fn get_line_index_sync(
|
||||||
&self,
|
&self,
|
||||||
specifier: &ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
) -> Option<LineIndex> {
|
) -> Option<LineIndex> {
|
||||||
|
@ -269,7 +256,7 @@ impl Inner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_navigation_tree(
|
pub(crate) async fn get_navigation_tree(
|
||||||
&mut self,
|
&mut self,
|
||||||
specifier: &ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
) -> Result<tsc::NavigationTree, AnyError> {
|
) -> Result<tsc::NavigationTree, AnyError> {
|
||||||
|
@ -277,9 +264,19 @@ impl Inner {
|
||||||
"get_navigation_tree",
|
"get_navigation_tree",
|
||||||
Some(json!({ "specifier": specifier })),
|
Some(json!({ "specifier": specifier })),
|
||||||
);
|
);
|
||||||
if let Some(navigation_tree) = self.navigation_trees.get(specifier) {
|
let maybe_navigation_tree = if specifier.scheme() == "asset" {
|
||||||
self.performance.measure(mark);
|
self
|
||||||
Ok(navigation_tree.clone())
|
.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 {
|
} else {
|
||||||
let navigation_tree: tsc::NavigationTree = self
|
let navigation_tree: tsc::NavigationTree = self
|
||||||
.ts_server
|
.ts_server
|
||||||
|
@ -288,13 +285,24 @@ impl Inner {
|
||||||
tsc::RequestMethod::GetNavigationTree(specifier.clone()),
|
tsc::RequestMethod::GetNavigationTree(specifier.clone()),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
if specifier.scheme() == "asset" {
|
||||||
self
|
self
|
||||||
.navigation_trees
|
.assets
|
||||||
.insert(specifier.clone(), navigation_tree.clone());
|
.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);
|
self.performance.measure(mark);
|
||||||
Ok(navigation_tree)
|
Ok(navigation_tree)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn snapshot(&self) -> LspResult<StateSnapshot> {
|
pub(crate) fn snapshot(&self) -> LspResult<StateSnapshot> {
|
||||||
Ok(StateSnapshot {
|
Ok(StateSnapshot {
|
||||||
|
@ -674,7 +682,6 @@ impl Inner {
|
||||||
}
|
}
|
||||||
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
||||||
self.documents.close(&specifier);
|
self.documents.close(&specifier);
|
||||||
self.navigation_trees.remove(&specifier);
|
|
||||||
|
|
||||||
if self.documents.is_diagnosable(&specifier) {
|
if self.documents.is_diagnosable(&specifier) {
|
||||||
if let Err(err) = self.diagnostics_server.update() {
|
if let Err(err) = self.diagnostics_server.update() {
|
||||||
|
@ -1090,302 +1097,36 @@ impl Inner {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mark = self.performance.mark("code_lens", Some(¶ms));
|
let mark = self.performance.mark("code_lens", Some(¶ms));
|
||||||
let line_index = self.get_line_index_sync(&specifier).unwrap();
|
let code_lenses = code_lens::tsc_code_lenses(&specifier, self)
|
||||||
let navigation_tree =
|
.await
|
||||||
self.get_navigation_tree(&specifier).await.map_err(|err| {
|
.map_err(|err| {
|
||||||
error!("Failed to retrieve nav tree: {}", err);
|
error!("Error getting code lenses for \"{}\": {}", specifier, err);
|
||||||
LspError::invalid_request()
|
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);
|
self.performance.measure(mark);
|
||||||
Ok(Some(Rc::try_unwrap(cl).unwrap().into_inner()))
|
|
||||||
|
Ok(Some(code_lenses))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn code_lens_resolve(
|
async fn code_lens_resolve(
|
||||||
&mut self,
|
&mut self,
|
||||||
params: CodeLens,
|
code_lens: CodeLens,
|
||||||
) -> LspResult<CodeLens> {
|
) -> LspResult<CodeLens> {
|
||||||
let mark = self.performance.mark("code_lens_resolve", Some(¶ms));
|
let mark = self.performance.mark("code_lens_resolve", Some(&code_lens));
|
||||||
if let Some(data) = params.data.clone() {
|
let result = if code_lens.data.is_some() {
|
||||||
let code_lens_data: CodeLensData = serde_json::from_value(data)
|
code_lens::resolve_code_lens(code_lens, self)
|
||||||
.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
|
.await
|
||||||
.map_err(|err| {
|
.map_err(|err| {
|
||||||
error!("Error processing TypeScript request: {}", err);
|
error!("Error resolving code lens: {}", err);
|
||||||
LspError::internal_error()
|
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 {
|
} 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)
|
|
||||||
} else {
|
|
||||||
self.performance.measure(mark);
|
|
||||||
Err(LspError::invalid_params(
|
Err(LspError::invalid_params(
|
||||||
"Code lens is missing the \"data\" property.",
|
"Code lens is missing the \"data\" property.",
|
||||||
))
|
))
|
||||||
}
|
};
|
||||||
|
self.performance.measure(mark);
|
||||||
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn document_highlight(
|
async fn document_highlight(
|
||||||
|
|
|
@ -6,6 +6,7 @@ use lspower::Server;
|
||||||
|
|
||||||
mod analysis;
|
mod analysis;
|
||||||
mod capabilities;
|
mod capabilities;
|
||||||
|
mod code_lens;
|
||||||
mod completions;
|
mod completions;
|
||||||
mod config;
|
mod config;
|
||||||
mod diagnostics;
|
mod diagnostics;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
use super::analysis;
|
use super::analysis;
|
||||||
use super::text::LineIndex;
|
use super::text::LineIndex;
|
||||||
|
use super::tsc;
|
||||||
|
|
||||||
use crate::file_fetcher::get_source_from_bytes;
|
use crate::file_fetcher::get_source_from_bytes;
|
||||||
use crate::file_fetcher::map_content_type;
|
use crate::file_fetcher::map_content_type;
|
||||||
|
@ -15,6 +16,7 @@ use crate::program_state::ProgramState;
|
||||||
use crate::specifier_handler::FetchHandler;
|
use crate::specifier_handler::FetchHandler;
|
||||||
use crate::text_encoding;
|
use crate::text_encoding;
|
||||||
|
|
||||||
|
use deno_core::error::anyhow;
|
||||||
use deno_core::error::AnyError;
|
use deno_core::error::AnyError;
|
||||||
use deno_core::serde_json;
|
use deno_core::serde_json;
|
||||||
use deno_core::ModuleSpecifier;
|
use deno_core::ModuleSpecifier;
|
||||||
|
@ -26,6 +28,7 @@ use std::path::PathBuf;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
use tsc::NavigationTree;
|
||||||
|
|
||||||
pub async fn cache(
|
pub async fn cache(
|
||||||
specifier: &ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
|
@ -104,6 +107,7 @@ struct Metadata {
|
||||||
dependencies: Option<HashMap<String, analysis::Dependency>>,
|
dependencies: Option<HashMap<String, analysis::Dependency>>,
|
||||||
length_utf16: usize,
|
length_utf16: usize,
|
||||||
line_index: LineIndex,
|
line_index: LineIndex,
|
||||||
|
maybe_navigation_tree: Option<tsc::NavigationTree>,
|
||||||
maybe_types: Option<analysis::ResolvedDependency>,
|
maybe_types: Option<analysis::ResolvedDependency>,
|
||||||
maybe_warning: Option<String>,
|
maybe_warning: Option<String>,
|
||||||
media_type: MediaType,
|
media_type: MediaType,
|
||||||
|
@ -139,6 +143,7 @@ impl Metadata {
|
||||||
dependencies,
|
dependencies,
|
||||||
length_utf16: source.encode_utf16().count(),
|
length_utf16: source.encode_utf16().count(),
|
||||||
line_index,
|
line_index,
|
||||||
|
maybe_navigation_tree: None,
|
||||||
maybe_types,
|
maybe_types,
|
||||||
maybe_warning,
|
maybe_warning,
|
||||||
media_type: media_type.to_owned(),
|
media_type: media_type.to_owned(),
|
||||||
|
@ -197,6 +202,13 @@ impl Sources {
|
||||||
self.0.lock().unwrap().get_media_type(specifier)
|
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(
|
pub fn get_script_version(
|
||||||
&self,
|
&self,
|
||||||
specifier: &ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
|
@ -223,6 +235,18 @@ impl Sources {
|
||||||
pub fn specifiers(&self) -> Vec<ModuleSpecifier> {
|
pub fn specifiers(&self) -> Vec<ModuleSpecifier> {
|
||||||
self.0.lock().unwrap().metadata.keys().cloned().collect()
|
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 {
|
impl Inner {
|
||||||
|
@ -343,6 +367,16 @@ impl Inner {
|
||||||
Some(metadata)
|
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> {
|
fn get_path(&mut self, specifier: &ModuleSpecifier) -> Option<PathBuf> {
|
||||||
if specifier.scheme() == "file" {
|
if specifier.scheme() == "file" {
|
||||||
specifier.to_file_path().ok()
|
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)]
|
#[cfg(test)]
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||||
|
|
||||||
use super::analysis::CodeLensSource;
|
|
||||||
use super::analysis::ResolvedDependency;
|
use super::analysis::ResolvedDependency;
|
||||||
use super::analysis::ResolvedDependencyErr;
|
use super::analysis::ResolvedDependencyErr;
|
||||||
|
use super::code_lens;
|
||||||
use super::config;
|
use super::config;
|
||||||
use super::language_server;
|
use super::language_server;
|
||||||
use super::language_server::StateSnapshot;
|
use super::language_server::StateSnapshot;
|
||||||
|
@ -103,6 +103,7 @@ pub struct AssetDocument {
|
||||||
pub text: String,
|
pub text: String,
|
||||||
pub length: usize,
|
pub length: usize,
|
||||||
pub line_index: LineIndex,
|
pub line_index: LineIndex,
|
||||||
|
pub maybe_navigation_tree: Option<NavigationTree>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AssetDocument {
|
impl AssetDocument {
|
||||||
|
@ -112,6 +113,7 @@ impl AssetDocument {
|
||||||
text: text.to_string(),
|
text: text.to_string(),
|
||||||
length: text.encode_utf16().count(),
|
length: text.encode_utf16().count(),
|
||||||
line_index: LineIndex::new(text),
|
line_index: LineIndex::new(text),
|
||||||
|
maybe_navigation_tree: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,6 +152,22 @@ impl Assets {
|
||||||
) -> Option<Option<AssetDocument>> {
|
) -> Option<Option<AssetDocument>> {
|
||||||
self.0.insert(k, v)
|
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
|
/// Optionally returns an internal asset, first checking for any static assets
|
||||||
|
@ -610,7 +628,7 @@ impl NavigationTree {
|
||||||
&self,
|
&self,
|
||||||
line_index: &LineIndex,
|
line_index: &LineIndex,
|
||||||
specifier: &ModuleSpecifier,
|
specifier: &ModuleSpecifier,
|
||||||
source: &CodeLensSource,
|
source: &code_lens::CodeLensSource,
|
||||||
) -> lsp::CodeLens {
|
) -> lsp::CodeLens {
|
||||||
let range = if let Some(name_span) = &self.name_span {
|
let range = if let Some(name_span) = &self.name_span {
|
||||||
name_span.to_range(line_index)
|
name_span.to_range(line_index)
|
||||||
|
|
|
@ -1092,7 +1092,7 @@ fn lsp_code_lens_impl() {
|
||||||
"uri": "file:///a/file.ts",
|
"uri": "file:///a/file.ts",
|
||||||
"languageId": "typescript",
|
"languageId": "typescript",
|
||||||
"version": 1,
|
"version": 1,
|
||||||
"text": "interface A {\n b(): void;\n}\n\nclass B implements A {\n b() {\n console.log(\"b\");\n }\n}\n"
|
"text": "interface A {\n b(): void;\n}\n\nclass B implements A {\n b() {\n console.log(\"b\");\n }\n}\n\ninterface C {\n c: string;\n}\n"
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
@ -1137,6 +1137,47 @@ fn lsp_code_lens_impl() {
|
||||||
maybe_res,
|
maybe_res,
|
||||||
Some(load_fixture("code_lens_resolve_response_impl.json"))
|
Some(load_fixture("code_lens_resolve_response_impl.json"))
|
||||||
);
|
);
|
||||||
|
let (maybe_res, maybe_err) = client
|
||||||
|
.write_request::<_, _, Value>(
|
||||||
|
"codeLens/resolve",
|
||||||
|
json!({
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 10,
|
||||||
|
"character": 10
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 10,
|
||||||
|
"character": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"specifier": "file:///a/file.ts",
|
||||||
|
"source": "implementations"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(maybe_err.is_none());
|
||||||
|
assert_eq!(
|
||||||
|
maybe_res,
|
||||||
|
Some(json!({
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 10,
|
||||||
|
"character": 10
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 10,
|
||||||
|
"character": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"title": "0 implementations",
|
||||||
|
"command": ""
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
);
|
||||||
shutdown(&mut client);
|
shutdown(&mut client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1225,6 +1266,79 @@ fn lsp_code_lens_non_doc_nav_tree() {
|
||||||
shutdown(&mut client);
|
shutdown(&mut client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn lsp_nav_tree_updates() {
|
||||||
|
let mut client = init("initialize_params.json");
|
||||||
|
did_open(
|
||||||
|
&mut client,
|
||||||
|
json!({
|
||||||
|
"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\ninterface C {\n c: string;\n}\n"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
let (maybe_res, maybe_err) = client
|
||||||
|
.write_request(
|
||||||
|
"textDocument/codeLens",
|
||||||
|
json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "file:///a/file.ts"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(maybe_err.is_none());
|
||||||
|
assert_eq!(
|
||||||
|
maybe_res,
|
||||||
|
Some(load_fixture("code_lens_response_impl.json"))
|
||||||
|
);
|
||||||
|
client
|
||||||
|
.write_notification(
|
||||||
|
"textDocument/didChange",
|
||||||
|
json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "file:///a/file.ts",
|
||||||
|
"version": 2
|
||||||
|
},
|
||||||
|
"contentChanges": [
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 10,
|
||||||
|
"character": 0
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 13,
|
||||||
|
"character": 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"text": ""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let (maybe_res, maybe_err) = client
|
||||||
|
.write_request(
|
||||||
|
"textDocument/codeLens",
|
||||||
|
json!({
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "file:///a/file.ts"
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert!(maybe_err.is_none());
|
||||||
|
assert_eq!(
|
||||||
|
maybe_res,
|
||||||
|
Some(load_fixture("code_lens_response_changed.json"))
|
||||||
|
);
|
||||||
|
shutdown(&mut client);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn lsp_signature_help() {
|
fn lsp_signature_help() {
|
||||||
let mut client = init("initialize_params.json");
|
let mut client = init("initialize_params.json");
|
||||||
|
|
50
cli/tests/lsp/code_lens_response_changed.json
Normal file
50
cli/tests/lsp/code_lens_response_changed.json
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
|
@ -46,5 +46,53 @@
|
||||||
"specifier": "file:///a/file.ts",
|
"specifier": "file:///a/file.ts",
|
||||||
"source": "references"
|
"source": "references"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 10,
|
||||||
|
"character": 10
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 10,
|
||||||
|
"character": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"specifier": "file:///a/file.ts",
|
||||||
|
"source": "implementations"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 10,
|
||||||
|
"character": 10
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 10,
|
||||||
|
"character": 11
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"specifier": "file:///a/file.ts",
|
||||||
|
"source": "references"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 11,
|
||||||
|
"character": 2
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 11,
|
||||||
|
"character": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"data": {
|
||||||
|
"specifier": "file:///a/file.ts",
|
||||||
|
"source": "references"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in a new issue