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::text::LineIndex;
use super::tsc::NavigationTree;
use crate::import_map::ImportMap;
use crate::media_type::MediaType;
@ -34,7 +33,6 @@ impl IndexValid {
pub struct DocumentData {
bytes: Option<Vec<u8>>,
line_index: Option<LineIndex>,
navigation_tree: Option<NavigationTree>,
dependencies: Option<HashMap<String, analysis::Dependency>>,
version: Option<i32>,
}
@ -74,7 +72,6 @@ impl DocumentData {
} else {
Some(LineIndex::new(&content))
};
self.navigation_tree = None;
Ok(())
}
@ -190,14 +187,6 @@ impl DocumentCache {
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(
&mut self,
specifier: ModuleSpecifier,
@ -229,22 +218,6 @@ impl DocumentCache {
.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> {
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.
use deno_core::error::anyhow;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
use deno_core::serde::Deserialize;
use deno_core::serde::Serialize;
@ -86,6 +85,8 @@ pub(crate) struct Inner {
maybe_import_map: Option<ImportMap>,
/// The URL for the import map which is used to determine relative imports.
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.
performance: Performance,
/// Cached sources that are read-only.
@ -119,6 +120,7 @@ impl Inner {
maybe_config_uri: Default::default(),
maybe_import_map: Default::default(),
maybe_import_map_uri: Default::default(),
navigation_trees: Default::default(),
performance: Default::default(),
sources,
ts_fixable_diagnostics: Default::default(),
@ -184,33 +186,24 @@ impl Inner {
&mut self,
specifier: &ModuleSpecifier,
) -> Result<tsc::NavigationTree, AnyError> {
if self.documents.contains(specifier) {
let mark = self.performance.mark("get_navigation_tree");
if let Some(navigation_tree) = self.documents.navigation_tree(specifier) {
self.performance.measure(mark);
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)
}
let mark = self.performance.mark("get_navigation_tree");
if let Some(navigation_tree) = self.navigation_trees.get(specifier) {
self.performance.measure(mark);
Ok(navigation_tree.clone())
} else {
Err(custom_error(
"NotFound",
format!("The document \"{}\" was unexpectedly not found.", specifier),
))
let res = self
.ts_server
.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);
}
// there are scenarios where local documents with a nav tree are opened in
// the editor
self.navigation_trees.remove(&specifier);
self.performance.measure(mark);
// TODO(@kitsonk): how to better lazily do this?
@ -672,6 +668,7 @@ impl Inner {
{
error!("{}", err);
}
self.navigation_trees.remove(&specifier);
self.performance.measure(mark);
// TODO(@kitsonk): how to better lazily do this?
@ -690,12 +687,13 @@ impl Inner {
}
let specifier = utils::normalize_url(params.text_document.uri);
self.documents.close(&specifier);
self.navigation_trees.remove(&specifier);
self.performance.measure(mark);
// TODO(@kitsonk): how to better lazily do this?
if let Err(err) = self.prepare_diagnostics().await {
error!("{}", err);
}
self.performance.measure(mark);
}
async fn did_change_configuration(
@ -713,7 +711,7 @@ impl Inner {
.await
.map(|vec| vec.get(0).cloned())
.unwrap_or_else(|err| {
error!("failed to fetch the extension settings {:?}", err);
error!("failed to fetch the extension settings {}", err);
None
})
} else {
@ -1026,7 +1024,7 @@ impl Inner {
let line_index = self.get_line_index_sync(&specifier).unwrap();
let navigation_tree =
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()
})?;
@ -1191,11 +1189,16 @@ impl Inner {
} else {
"1 implementation".to_string()
};
let url = utils::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(code_lens_data.specifier).unwrap(),
serde_json::to_value(url).unwrap(),
serde_json::to_value(params.range.start).unwrap(),
serde_json::to_value(locations).unwrap(),
]),
@ -1272,11 +1275,16 @@ impl Inner {
} else {
"1 reference".to_string()
};
let url = utils::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(code_lens_data.specifier).unwrap(),
serde_json::to_value(url).unwrap(),
serde_json::to_value(params.range.start).unwrap(),
serde_json::to_value(locations).unwrap(),
]),
@ -1531,13 +1539,13 @@ impl Inner {
.request(self.snapshot(), req)
.await
.map_err(|err| {
error!("Failed to request to tsserver {:#?}", err);
error!("Failed to request to tsserver {}", err);
LspError::invalid_request()
})?;
let maybe_implementations = serde_json::from_value::<Option<Vec<tsc::ImplementationLocation>>>(res)
.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()
})?;
@ -1596,7 +1604,7 @@ impl Inner {
.request(self.snapshot(), req)
.await
.map_err(|err| {
error!("Failed to request to tsserver {:#?}", err);
error!("Failed to request to tsserver {}", err);
LspError::invalid_request()
})?;
@ -1605,7 +1613,7 @@ impl Inner {
>(res)
.map_err(|err| {
error!(
"Failed to deserialize tsserver response to Vec<RenameLocation> {:#?}",
"Failed to deserialize tsserver response to Vec<RenameLocation> {}",
err
);
LspError::internal_error()
@ -1617,7 +1625,7 @@ impl Inner {
.into_workspace_edit(&params.new_name, self)
.await
.map_err(|err| {
error!("Failed to get workspace edits: {:#?}", err);
error!("Failed to get workspace edits: {}", err);
LspError::internal_error()
})?;
self.performance.measure(mark);
@ -1637,7 +1645,7 @@ impl Inner {
"deno/cache" => match params.map(serde_json::from_value) {
Some(Ok(params)) => Ok(Some(
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()
})?,
)),
@ -1650,7 +1658,7 @@ impl Inner {
serde_json::to_value(self.virtual_text_document(params).await?)
.map_err(|err| {
error!(
"Failed to serialize virtual_text_document response: {:#?}",
"Failed to serialize virtual_text_document response: {}",
err
);
LspError::internal_error()
@ -2584,6 +2592,51 @@ mod tests {
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]
async fn test_code_actions() {
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())
}
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
/// converted into proper module specifiers.
pub fn normalize_url(url: Url) -> ModuleSpecifier {
@ -40,6 +53,23 @@ pub fn normalize_url(url: Url) -> ModuleSpecifier {
mod tests {
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]
fn test_normalize_url() {
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",
"languageId": "typescript",
"version": 1,
"text": "console.log(Date.now());\n"
"text": "console.log(\"hello deno!\");\n"
}
}
}