1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-11 08:33:43 -05:00

fix(lsp): handle code lenses for non-documents (#9454)

This commit is contained in:
Kitson Kelly 2021-02-09 20:48:53 +11:00 committed by GitHub
parent 97d5ef2950
commit d95666cae0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 154 additions and 67 deletions

View file

@ -2,7 +2,6 @@
use super::analysis; use super::analysis;
use super::text::LineIndex; use super::text::LineIndex;
use super::tsc::NavigationTree;
use crate::import_map::ImportMap; use crate::import_map::ImportMap;
use crate::media_type::MediaType; use crate::media_type::MediaType;
@ -34,7 +33,6 @@ impl IndexValid {
pub struct DocumentData { pub struct DocumentData {
bytes: Option<Vec<u8>>, bytes: Option<Vec<u8>>,
line_index: Option<LineIndex>, line_index: Option<LineIndex>,
navigation_tree: Option<NavigationTree>,
dependencies: Option<HashMap<String, analysis::Dependency>>, dependencies: Option<HashMap<String, analysis::Dependency>>,
version: Option<i32>, version: Option<i32>,
} }
@ -74,7 +72,6 @@ impl DocumentData {
} else { } else {
Some(LineIndex::new(&content)) Some(LineIndex::new(&content))
}; };
self.navigation_tree = None;
Ok(()) Ok(())
} }
@ -190,14 +187,6 @@ impl DocumentCache {
doc.line_index.clone() doc.line_index.clone()
} }
pub fn navigation_tree(
&self,
specifier: &ModuleSpecifier,
) -> Option<NavigationTree> {
let doc = self.docs.get(specifier)?;
doc.navigation_tree.clone()
}
pub fn open( pub fn open(
&mut self, &mut self,
specifier: ModuleSpecifier, specifier: ModuleSpecifier,
@ -229,22 +218,6 @@ impl DocumentCache {
.collect() .collect()
} }
pub fn set_navigation_tree(
&mut self,
specifier: &ModuleSpecifier,
navigation_tree: NavigationTree,
) -> Result<(), AnyError> {
if let Some(mut doc) = self.docs.get_mut(specifier) {
doc.navigation_tree = Some(navigation_tree);
Ok(())
} else {
Err(custom_error(
"NotFound",
"The document \"{}\" was unexpectedly missing.",
))
}
}
pub fn version(&self, specifier: &ModuleSpecifier) -> Option<i32> { pub fn version(&self, specifier: &ModuleSpecifier) -> Option<i32> {
self.docs.get(specifier).and_then(|doc| doc.version) self.docs.get(specifier).and_then(|doc| doc.version)
} }

View file

@ -1,7 +1,6 @@
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
use deno_core::error::anyhow; use deno_core::error::anyhow;
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::Serialize;
@ -86,6 +85,8 @@ pub(crate) struct Inner {
maybe_import_map: Option<ImportMap>, 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.
@ -119,6 +120,7 @@ impl Inner {
maybe_config_uri: Default::default(), maybe_config_uri: Default::default(),
maybe_import_map: Default::default(), maybe_import_map: Default::default(),
maybe_import_map_uri: Default::default(), maybe_import_map_uri: Default::default(),
navigation_trees: Default::default(),
performance: Default::default(), performance: Default::default(),
sources, sources,
ts_fixable_diagnostics: Default::default(), ts_fixable_diagnostics: Default::default(),
@ -184,33 +186,24 @@ impl Inner {
&mut self, &mut self,
specifier: &ModuleSpecifier, specifier: &ModuleSpecifier,
) -> Result<tsc::NavigationTree, AnyError> { ) -> Result<tsc::NavigationTree, AnyError> {
if self.documents.contains(specifier) { let mark = self.performance.mark("get_navigation_tree");
let mark = self.performance.mark("get_navigation_tree"); if let Some(navigation_tree) = self.navigation_trees.get(specifier) {
if let Some(navigation_tree) = self.documents.navigation_tree(specifier) { self.performance.measure(mark);
self.performance.measure(mark); Ok(navigation_tree.clone())
Ok(navigation_tree)
} else {
let res = self
.ts_server
.request(
self.snapshot(),
tsc::RequestMethod::GetNavigationTree(specifier.clone()),
)
.await
.unwrap();
let navigation_tree: tsc::NavigationTree =
serde_json::from_value(res).unwrap();
self
.documents
.set_navigation_tree(specifier, navigation_tree.clone())?;
self.performance.measure(mark);
Ok(navigation_tree)
}
} else { } else {
Err(custom_error( let res = self
"NotFound", .ts_server
format!("The document \"{}\" was unexpectedly not found.", specifier), .request(
)) self.snapshot(),
tsc::RequestMethod::GetNavigationTree(specifier.clone()),
)
.await?;
let navigation_tree: tsc::NavigationTree = serde_json::from_value(res)?;
self
.navigation_trees
.insert(specifier.clone(), navigation_tree.clone());
self.performance.measure(mark);
Ok(navigation_tree)
} }
} }
@ -648,6 +641,9 @@ impl Inner {
{ {
error!("{}", err); error!("{}", err);
} }
// there are scenarios where local documents with a nav tree are opened in
// the editor
self.navigation_trees.remove(&specifier);
self.performance.measure(mark); self.performance.measure(mark);
// TODO(@kitsonk): how to better lazily do this? // TODO(@kitsonk): how to better lazily do this?
@ -672,6 +668,7 @@ impl Inner {
{ {
error!("{}", err); error!("{}", err);
} }
self.navigation_trees.remove(&specifier);
self.performance.measure(mark); self.performance.measure(mark);
// TODO(@kitsonk): how to better lazily do this? // TODO(@kitsonk): how to better lazily do this?
@ -690,12 +687,13 @@ impl Inner {
} }
let specifier = utils::normalize_url(params.text_document.uri); let specifier = utils::normalize_url(params.text_document.uri);
self.documents.close(&specifier); self.documents.close(&specifier);
self.navigation_trees.remove(&specifier);
self.performance.measure(mark);
// TODO(@kitsonk): how to better lazily do this? // TODO(@kitsonk): how to better lazily do this?
if let Err(err) = self.prepare_diagnostics().await { if let Err(err) = self.prepare_diagnostics().await {
error!("{}", err); error!("{}", err);
} }
self.performance.measure(mark);
} }
async fn did_change_configuration( async fn did_change_configuration(
@ -713,7 +711,7 @@ impl Inner {
.await .await
.map(|vec| vec.get(0).cloned()) .map(|vec| vec.get(0).cloned())
.unwrap_or_else(|err| { .unwrap_or_else(|err| {
error!("failed to fetch the extension settings {:?}", err); error!("failed to fetch the extension settings {}", err);
None None
}) })
} else { } else {
@ -1026,7 +1024,7 @@ impl Inner {
let line_index = self.get_line_index_sync(&specifier).unwrap(); let line_index = self.get_line_index_sync(&specifier).unwrap();
let navigation_tree = let navigation_tree =
self.get_navigation_tree(&specifier).await.map_err(|err| { self.get_navigation_tree(&specifier).await.map_err(|err| {
error!("Failed to retrieve nav tree: {:#?}", err); error!("Failed to retrieve nav tree: {}", err);
LspError::invalid_request() LspError::invalid_request()
})?; })?;
@ -1191,11 +1189,16 @@ impl Inner {
} else { } else {
"1 implementation".to_string() "1 implementation".to_string()
}; };
let url = utils::normalize_specifier(&code_lens_data.specifier)
.map_err(|err| {
error!("{}", err);
LspError::internal_error()
})?;
Command { Command {
title, title,
command: "deno.showReferences".to_string(), command: "deno.showReferences".to_string(),
arguments: Some(vec![ arguments: Some(vec![
serde_json::to_value(code_lens_data.specifier).unwrap(), serde_json::to_value(url).unwrap(),
serde_json::to_value(params.range.start).unwrap(), serde_json::to_value(params.range.start).unwrap(),
serde_json::to_value(locations).unwrap(), serde_json::to_value(locations).unwrap(),
]), ]),
@ -1272,11 +1275,16 @@ impl Inner {
} else { } else {
"1 reference".to_string() "1 reference".to_string()
}; };
let url = utils::normalize_specifier(&code_lens_data.specifier)
.map_err(|err| {
error!("{}", err);
LspError::internal_error()
})?;
Command { Command {
title, title,
command: "deno.showReferences".to_string(), command: "deno.showReferences".to_string(),
arguments: Some(vec![ arguments: Some(vec![
serde_json::to_value(code_lens_data.specifier).unwrap(), serde_json::to_value(url).unwrap(),
serde_json::to_value(params.range.start).unwrap(), serde_json::to_value(params.range.start).unwrap(),
serde_json::to_value(locations).unwrap(), serde_json::to_value(locations).unwrap(),
]), ]),
@ -1531,13 +1539,13 @@ impl Inner {
.request(self.snapshot(), req) .request(self.snapshot(), req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Failed to request to tsserver {:#?}", err); error!("Failed to request to tsserver {}", err);
LspError::invalid_request() LspError::invalid_request()
})?; })?;
let maybe_implementations = serde_json::from_value::<Option<Vec<tsc::ImplementationLocation>>>(res) let maybe_implementations = serde_json::from_value::<Option<Vec<tsc::ImplementationLocation>>>(res)
.map_err(|err| { .map_err(|err| {
error!("Failed to deserialized tsserver response to Vec<ImplementationLocation> {:#?}", err); error!("Failed to deserialized tsserver response to Vec<ImplementationLocation> {}", err);
LspError::internal_error() LspError::internal_error()
})?; })?;
@ -1596,7 +1604,7 @@ impl Inner {
.request(self.snapshot(), req) .request(self.snapshot(), req)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Failed to request to tsserver {:#?}", err); error!("Failed to request to tsserver {}", err);
LspError::invalid_request() LspError::invalid_request()
})?; })?;
@ -1605,7 +1613,7 @@ impl Inner {
>(res) >(res)
.map_err(|err| { .map_err(|err| {
error!( error!(
"Failed to deserialize tsserver response to Vec<RenameLocation> {:#?}", "Failed to deserialize tsserver response to Vec<RenameLocation> {}",
err err
); );
LspError::internal_error() LspError::internal_error()
@ -1617,7 +1625,7 @@ impl Inner {
.into_workspace_edit(&params.new_name, self) .into_workspace_edit(&params.new_name, self)
.await .await
.map_err(|err| { .map_err(|err| {
error!("Failed to get workspace edits: {:#?}", err); error!("Failed to get workspace edits: {}", err);
LspError::internal_error() LspError::internal_error()
})?; })?;
self.performance.measure(mark); self.performance.measure(mark);
@ -1637,7 +1645,7 @@ impl Inner {
"deno/cache" => match params.map(serde_json::from_value) { "deno/cache" => match params.map(serde_json::from_value) {
Some(Ok(params)) => Ok(Some( Some(Ok(params)) => Ok(Some(
serde_json::to_value(self.cache(params).await?).map_err(|err| { serde_json::to_value(self.cache(params).await?).map_err(|err| {
error!("Failed to serialize cache response: {:#?}", err); error!("Failed to serialize cache response: {}", err);
LspError::internal_error() LspError::internal_error()
})?, })?,
)), )),
@ -1650,7 +1658,7 @@ impl Inner {
serde_json::to_value(self.virtual_text_document(params).await?) serde_json::to_value(self.virtual_text_document(params).await?)
.map_err(|err| { .map_err(|err| {
error!( error!(
"Failed to serialize virtual_text_document response: {:#?}", "Failed to serialize virtual_text_document response: {}",
err err
); );
LspError::internal_error() LspError::internal_error()
@ -2584,6 +2592,51 @@ mod tests {
harness.run().await; harness.run().await;
} }
#[derive(Deserialize)]
struct CodeLensResponse {
pub result: Option<Vec<CodeLens>>,
}
#[derive(Deserialize)]
struct CodeLensResolveResponse {
pub result: CodeLens,
}
#[tokio::test]
async fn test_code_lens_non_doc_nav_tree() {
let mut harness = LspTestHarness::new(vec![
("initialize_request.json", LspResponse::RequestAny),
("initialized_notification.json", LspResponse::None),
("did_open_notification_asset.json", LspResponse::None),
(
"virtual_text_document_request.json",
LspResponse::RequestAny,
),
(
"code_lens_request_asset.json",
LspResponse::RequestAssert(|value| {
let resp: CodeLensResponse = serde_json::from_value(value).unwrap();
let lenses = resp.result.unwrap();
assert!(lenses.len() > 50);
}),
),
(
"code_lens_resolve_request_asset.json",
LspResponse::RequestAssert(|value| {
let resp: CodeLensResolveResponse =
serde_json::from_value(value).unwrap();
assert!(resp.result.command.is_some());
}),
),
(
"shutdown_request.json",
LspResponse::Request(3, json!(null)),
),
("exit_notification.json", LspResponse::None),
]);
harness.run().await;
}
#[tokio::test] #[tokio::test]
async fn test_code_actions() { async fn test_code_actions() {
let mut harness = LspTestHarness::new(vec![ let mut harness = LspTestHarness::new(vec![

View file

@ -16,6 +16,19 @@ pub fn normalize_file_name(file_name: &str) -> Result<Url, AnyError> {
Url::parse(&specifier_str).map_err(|err| err.into()) Url::parse(&specifier_str).map_err(|err| err.into())
} }
pub fn normalize_specifier(
specifier: &ModuleSpecifier,
) -> Result<Url, AnyError> {
let url = specifier.as_url();
if url.scheme() == "file" {
Ok(url.clone())
} else {
let specifier_str =
format!("deno:///{}", url.as_str().replacen("://", "/", 1));
Url::parse(&specifier_str).map_err(|err| err.into())
}
}
/// Normalize URLs from the client, where "virtual" `deno:///` URLs are /// Normalize URLs from the client, where "virtual" `deno:///` URLs are
/// converted into proper module specifiers. /// converted into proper module specifiers.
pub fn normalize_url(url: Url) -> ModuleSpecifier { pub fn normalize_url(url: Url) -> ModuleSpecifier {
@ -40,6 +53,23 @@ pub fn normalize_url(url: Url) -> ModuleSpecifier {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn test_normalize_file_name() {
let fixture = "https://deno.land/x/mod.ts";
let actual = normalize_file_name(fixture).unwrap();
let expected = Url::parse("deno:///https/deno.land/x/mod.ts").unwrap();
assert_eq!(actual, expected);
}
#[test]
fn test_normalize_specifier() {
let fixture =
ModuleSpecifier::resolve_url("https://deno.land/x/mod.ts").unwrap();
let actual = normalize_specifier(&fixture).unwrap();
let expected = Url::parse("deno:///https/deno.land/x/mod.ts").unwrap();
assert_eq!(actual, expected);
}
#[test] #[test]
fn test_normalize_url() { fn test_normalize_url() {
let fixture = Url::parse("deno:///https/deno.land/x/mod.ts").unwrap(); let fixture = Url::parse("deno:///https/deno.land/x/mod.ts").unwrap();

View file

@ -0,0 +1,10 @@
{
"jsonrpc": "2.0",
"id": 4,
"method": "textDocument/codeLens",
"params": {
"textDocument": {
"uri": "deno:/asset//lib.es2015.symbol.wellknown.d.ts"
}
}
}

View file

@ -0,0 +1,21 @@
{
"jsonrpc": "2.0",
"id": 5,
"method": "codeLens/resolve",
"params": {
"range": {
"start": {
"line": 93,
"character": 10
},
"end": {
"line": 93,
"character": 15
}
},
"data": {
"specifier": "asset:///lib.es2015.symbol.wellknown.d.ts",
"source": "implementations"
}
}
}

View file

@ -6,7 +6,7 @@
"uri": "file:///a/file.ts", "uri": "file:///a/file.ts",
"languageId": "typescript", "languageId": "typescript",
"version": 1, "version": 1,
"text": "console.log(Date.now());\n" "text": "console.log(\"hello deno!\");\n"
} }
} }
} }