mirror of
https://github.com/denoland/deno.git
synced 2025-01-18 11:53:59 -05:00
feat(lsp): add references code lens (#9316)
This commit is contained in:
parent
46d5843f75
commit
534531e4dd
13 changed files with 738 additions and 152 deletions
|
@ -9,6 +9,8 @@ use crate::module_graph::TypeScriptReference;
|
|||
use crate::tools::lint::create_linter;
|
||||
|
||||
use deno_core::error::AnyError;
|
||||
use deno_core::serde::Deserialize;
|
||||
use deno_core::serde::Serialize;
|
||||
use deno_core::ModuleSpecifier;
|
||||
use deno_lint::rules;
|
||||
use lspower::lsp;
|
||||
|
@ -249,6 +251,19 @@ pub fn analyze_dependencies(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub enum CodeLensSource {
|
||||
#[serde(rename = "references")]
|
||||
References,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeLensData {
|
||||
pub source: CodeLensSource,
|
||||
pub specifier: ModuleSpecifier,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
///! client.
|
||||
///!
|
||||
use lspower::lsp::ClientCapabilities;
|
||||
use lspower::lsp::CodeLensOptions;
|
||||
use lspower::lsp::CompletionOptions;
|
||||
use lspower::lsp::HoverProviderCapability;
|
||||
use lspower::lsp::ImplementationProviderCapability;
|
||||
|
@ -59,7 +60,9 @@ pub fn server_capabilities(
|
|||
document_symbol_provider: None,
|
||||
workspace_symbol_provider: None,
|
||||
code_action_provider: None,
|
||||
code_lens_provider: None,
|
||||
code_lens_provider: Some(CodeLensOptions {
|
||||
resolve_provider: Some(true),
|
||||
}),
|
||||
document_formatting_provider: Some(OneOf::Left(true)),
|
||||
document_range_formatting_provider: None,
|
||||
document_on_type_formatting_provider: None,
|
||||
|
|
|
@ -15,12 +15,25 @@ pub struct ClientCapabilities {
|
|||
pub workspace_did_change_watched_files: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CodeLensSettings {
|
||||
/// Flag for providing reference code lens.
|
||||
#[serde(default)]
|
||||
pub references: bool,
|
||||
/// Flag for providing reference code lens on all functions. For this to have
|
||||
/// an impact, the `references` flag needs to be `true`.
|
||||
#[serde(default)]
|
||||
pub references_all_functions: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct WorkspaceSettings {
|
||||
pub enable: bool,
|
||||
pub config: Option<String>,
|
||||
pub import_map: Option<String>,
|
||||
pub code_lens: Option<CodeLensSettings>,
|
||||
|
||||
#[serde(default)]
|
||||
pub lint: bool,
|
||||
|
@ -28,7 +41,36 @@ pub struct WorkspaceSettings {
|
|||
pub unstable: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
impl WorkspaceSettings {
|
||||
/// Determine if any code lenses are enabled at all. This allows short
|
||||
/// circuiting when there are no code lenses enabled.
|
||||
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
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enabled_code_lens_references(&self) -> bool {
|
||||
if let Some(code_lens) = &self.code_lens {
|
||||
code_lens.references
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn enabled_code_lens_references_all_functions(&self) -> bool {
|
||||
if let Some(code_lens) = &self.code_lens {
|
||||
code_lens.references_all_functions
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Config {
|
||||
pub client_capabilities: ClientCapabilities,
|
||||
pub root_uri: Option<Url>,
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
use super::analysis;
|
||||
use super::text::LineIndex;
|
||||
use super::tsc::NavigationTree;
|
||||
|
||||
use crate::import_map::ImportMap;
|
||||
use crate::media_type::MediaType;
|
||||
|
@ -33,6 +34,7 @@ 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>,
|
||||
}
|
||||
|
@ -72,6 +74,7 @@ impl DocumentData {
|
|||
} else {
|
||||
Some(LineIndex::new(&content))
|
||||
};
|
||||
self.navigation_tree = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
@ -187,6 +190,14 @@ 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,
|
||||
|
@ -218,6 +229,22 @@ 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)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// 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;
|
||||
|
@ -14,9 +15,12 @@ use lspower::jsonrpc::Result as LspResult;
|
|||
use lspower::lsp::request::*;
|
||||
use lspower::lsp::*;
|
||||
use lspower::Client;
|
||||
use regex::Regex;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use tokio::fs;
|
||||
|
||||
|
@ -25,6 +29,8 @@ use crate::import_map::ImportMap;
|
|||
use crate::tsc_config::parse_config;
|
||||
use crate::tsc_config::TsConfig;
|
||||
|
||||
use super::analysis::CodeLensData;
|
||||
use super::analysis::CodeLensSource;
|
||||
use super::capabilities;
|
||||
use super::config::Config;
|
||||
use super::diagnostics;
|
||||
|
@ -41,6 +47,10 @@ use super::tsc::AssetDocument;
|
|||
use super::tsc::TsServer;
|
||||
use super::utils;
|
||||
|
||||
lazy_static! {
|
||||
static ref EXPORT_MODIFIER: Regex = Regex::new(r"\bexport\b").unwrap();
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LanguageServer(Arc<tokio::sync::Mutex<Inner>>);
|
||||
|
||||
|
@ -162,6 +172,37 @@ impl Inner {
|
|||
maybe_line_index
|
||||
}
|
||||
|
||||
async fn get_navigation_tree(
|
||||
&mut self,
|
||||
specifier: &ModuleSpecifier,
|
||||
) -> Result<tsc::NavigationTree, AnyError> {
|
||||
if self.documents.contains(specifier) {
|
||||
if let Some(navigation_tree) = self.documents.navigation_tree(specifier) {
|
||||
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())?;
|
||||
Ok(navigation_tree)
|
||||
}
|
||||
} else {
|
||||
Err(custom_error(
|
||||
"NotFound",
|
||||
format!("The document \"{}\" was unexpectedly not found.", specifier),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn prepare_diagnostics(&mut self) -> Result<(), AnyError> {
|
||||
let (enabled, lint_enabled) = {
|
||||
let config = &self.config;
|
||||
|
@ -271,14 +312,13 @@ impl Inner {
|
|||
(maybe_changes, diagnostics_collection.clone())
|
||||
};
|
||||
if let Some(diagnostic_changes) = maybe_changes {
|
||||
let settings = self.config.settings.clone();
|
||||
for specifier in diagnostic_changes {
|
||||
// TODO(@kitsonk) not totally happy with the way we collect and store
|
||||
// different types of diagnostics and offer them up to the client, we
|
||||
// do need to send "empty" vectors though when a particular feature is
|
||||
// disabled, otherwise the client will not clear down previous
|
||||
// diagnostics
|
||||
let mut diagnostics: Vec<Diagnostic> = if settings.lint {
|
||||
let mut diagnostics: Vec<Diagnostic> = if self.config.settings.lint {
|
||||
diagnostics_collection
|
||||
.diagnostics_for(&specifier, &DiagnosticSource::Lint)
|
||||
.cloned()
|
||||
|
@ -778,6 +818,209 @@ impl Inner {
|
|||
}
|
||||
}
|
||||
|
||||
async fn code_lens(
|
||||
&mut self,
|
||||
params: CodeLensParams,
|
||||
) -> LspResult<Option<Vec<CodeLens>>> {
|
||||
if !self.enabled() || !self.config.settings.enabled_code_lens() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mark = self.performance.mark("code_lens");
|
||||
let specifier = utils::normalize_url(params.text_document.uri);
|
||||
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);
|
||||
LspError::invalid_request()
|
||||
})?;
|
||||
|
||||
// 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();
|
||||
|
||||
// TSC References Code Lens
|
||||
if self.config.settings.enabled_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 self
|
||||
.config
|
||||
.settings
|
||||
.enabled_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);
|
||||
Ok(Some(Rc::try_unwrap(cl).unwrap().into_inner()))
|
||||
}
|
||||
|
||||
async fn code_lens_resolve(
|
||||
&mut self,
|
||||
params: CodeLens,
|
||||
) -> LspResult<CodeLens> {
|
||||
let mark = self.performance.mark("code_lens_resolve");
|
||||
if let Some(data) = params.data.clone() {
|
||||
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::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 res =
|
||||
self.ts_server.request(self.snapshot(), req).await.map_err(
|
||||
|err| {
|
||||
error!("Error processing TypeScript request: {}", err);
|
||||
LspError::internal_error()
|
||||
},
|
||||
)?;
|
||||
let maybe_references: Option<Vec<tsc::ReferenceEntry>> =
|
||||
serde_json::from_value(res).map_err(|err| {
|
||||
error!("Error deserializing response: {}", 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 = ModuleSpecifier::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));
|
||||
}
|
||||
let command = if !locations.is_empty() {
|
||||
let title = if locations.len() > 1 {
|
||||
format!("{} references", locations.len())
|
||||
} else {
|
||||
"1 reference".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 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(
|
||||
"Code lens is missing the \"data\" property.",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
async fn document_highlight(
|
||||
&self,
|
||||
params: DocumentHighlightParams,
|
||||
|
@ -1195,6 +1438,17 @@ impl lspower::LanguageServer for LanguageServer {
|
|||
self.0.lock().await.hover(params).await
|
||||
}
|
||||
|
||||
async fn code_lens(
|
||||
&self,
|
||||
params: CodeLensParams,
|
||||
) -> LspResult<Option<Vec<CodeLens>>> {
|
||||
self.0.lock().await.code_lens(params).await
|
||||
}
|
||||
|
||||
async fn code_lens_resolve(&self, params: CodeLens) -> LspResult<CodeLens> {
|
||||
self.0.lock().await.code_lens_resolve(params).await
|
||||
}
|
||||
|
||||
async fn document_highlight(
|
||||
&self,
|
||||
params: DocumentHighlightParams,
|
||||
|
@ -1765,6 +2019,108 @@ mod tests {
|
|||
harness.run().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_code_lens_request() {
|
||||
let mut harness = LspTestHarness::new(vec![
|
||||
("initialize_request.json", LspResponse::RequestAny),
|
||||
("initialized_notification.json", LspResponse::None),
|
||||
(
|
||||
"did_open_notification_cl_references.json",
|
||||
LspResponse::None,
|
||||
),
|
||||
(
|
||||
"code_lens_request.json",
|
||||
LspResponse::Request(
|
||||
2,
|
||||
json!([
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 6,
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 7,
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"source": "references",
|
||||
},
|
||||
},
|
||||
{
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 2,
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 3,
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"source": "references",
|
||||
}
|
||||
}
|
||||
]),
|
||||
),
|
||||
),
|
||||
(
|
||||
"code_lens_resolve_request.json",
|
||||
LspResponse::Request(
|
||||
4,
|
||||
json!({
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 6,
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 7,
|
||||
}
|
||||
},
|
||||
"command": {
|
||||
"title": "1 reference",
|
||||
"command": "deno.showReferences",
|
||||
"arguments": [
|
||||
"file:///a/file.ts",
|
||||
{
|
||||
"line": 0,
|
||||
"character": 6,
|
||||
},
|
||||
[
|
||||
{
|
||||
"uri": "file:///a/file.ts",
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 12,
|
||||
"character": 14,
|
||||
},
|
||||
"end": {
|
||||
"line": 12,
|
||||
"character": 15,
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
]
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
(
|
||||
"shutdown_request.json",
|
||||
LspResponse::Request(3, json!(null)),
|
||||
),
|
||||
("exit_notification.json", LspResponse::None),
|
||||
]);
|
||||
harness.run().await;
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PerformanceAverages {
|
||||
averages: Vec<PerformanceAverage>,
|
||||
|
|
207
cli/lsp/tsc.rs
207
cli/lsp/tsc.rs
|
@ -1,5 +1,6 @@
|
|||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use super::analysis::CodeLensSource;
|
||||
use super::analysis::ResolvedDependency;
|
||||
use super::language_server::StateSnapshot;
|
||||
use super::text;
|
||||
|
@ -241,7 +242,7 @@ fn replace_links(text: &str) -> String {
|
|||
.to_string()
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[derive(Debug, Clone, Deserialize, PartialEq, Eq)]
|
||||
pub enum ScriptElementKind {
|
||||
#[serde(rename = "")]
|
||||
Unknown,
|
||||
|
@ -356,8 +357,8 @@ impl From<ScriptElementKind> for lsp::CompletionItemKind {
|
|||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct TextSpan {
|
||||
start: u32,
|
||||
length: u32,
|
||||
pub start: u32,
|
||||
pub length: u32,
|
||||
}
|
||||
|
||||
impl TextSpan {
|
||||
|
@ -480,6 +481,59 @@ impl DocumentSpan {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct NavigationTree {
|
||||
pub text: String,
|
||||
pub kind: ScriptElementKind,
|
||||
pub kind_modifiers: String,
|
||||
pub spans: Vec<TextSpan>,
|
||||
pub name_span: Option<TextSpan>,
|
||||
pub child_items: Option<Vec<NavigationTree>>,
|
||||
}
|
||||
|
||||
impl NavigationTree {
|
||||
pub fn to_code_lens(
|
||||
&self,
|
||||
line_index: &LineIndex,
|
||||
specifier: &ModuleSpecifier,
|
||||
source: &CodeLensSource,
|
||||
) -> lsp::CodeLens {
|
||||
lsp::CodeLens {
|
||||
range: self.name_span.clone().unwrap().to_range(line_index),
|
||||
command: None,
|
||||
data: Some(json!({
|
||||
"specifier": specifier,
|
||||
"source": source
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn walk<F>(&self, callback: &F)
|
||||
where
|
||||
F: Fn(&NavigationTree, Option<&NavigationTree>),
|
||||
{
|
||||
callback(self, None);
|
||||
if let Some(child_items) = &self.child_items {
|
||||
for child in child_items {
|
||||
child.walk_child(callback, self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn walk_child<F>(&self, callback: &F, parent: &NavigationTree)
|
||||
where
|
||||
F: Fn(&NavigationTree, Option<&NavigationTree>),
|
||||
{
|
||||
callback(self, Some(parent));
|
||||
if let Some(child_items) = &self.child_items {
|
||||
for child in child_items {
|
||||
child.walk_child(callback, self);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ImplementationLocation {
|
||||
|
@ -1157,24 +1211,26 @@ pub struct UserPreferences {
|
|||
pub enum RequestMethod {
|
||||
/// Configure the compilation settings for the server.
|
||||
Configure(TsConfig),
|
||||
/// Retrieve the text of an assets that exists in memory in the isolate.
|
||||
GetAsset(ModuleSpecifier),
|
||||
/// Return diagnostics for given file.
|
||||
GetDiagnostics(Vec<ModuleSpecifier>),
|
||||
/// Return quick info at position (hover information).
|
||||
GetQuickInfo((ModuleSpecifier, u32)),
|
||||
/// Return document highlights at position.
|
||||
GetDocumentHighlights((ModuleSpecifier, u32, Vec<ModuleSpecifier>)),
|
||||
/// Get document references for a specific position.
|
||||
GetReferences((ModuleSpecifier, u32)),
|
||||
/// Get declaration information for a specific position.
|
||||
GetDefinition((ModuleSpecifier, u32)),
|
||||
/// Get completion information at a given position (IntelliSense).
|
||||
GetCompletions((ModuleSpecifier, u32, UserPreferences)),
|
||||
/// Get implementation information for a specific position.
|
||||
GetImplementation((ModuleSpecifier, u32)),
|
||||
/// Get rename locations at a given position.
|
||||
FindRenameLocations((ModuleSpecifier, u32, bool, bool, bool)),
|
||||
/// Retrieve the text of an assets that exists in memory in the isolate.
|
||||
GetAsset(ModuleSpecifier),
|
||||
/// Get completion information at a given position (IntelliSense).
|
||||
GetCompletions((ModuleSpecifier, u32, UserPreferences)),
|
||||
/// Get declaration information for a specific position.
|
||||
GetDefinition((ModuleSpecifier, u32)),
|
||||
/// Return diagnostics for given file.
|
||||
GetDiagnostics(Vec<ModuleSpecifier>),
|
||||
/// Return document highlights at position.
|
||||
GetDocumentHighlights((ModuleSpecifier, u32, Vec<ModuleSpecifier>)),
|
||||
/// Get implementation information for a specific position.
|
||||
GetImplementation((ModuleSpecifier, u32)),
|
||||
/// Get a "navigation tree" for a specifier.
|
||||
GetNavigationTree(ModuleSpecifier),
|
||||
/// Return quick info at position (hover information).
|
||||
GetQuickInfo((ModuleSpecifier, u32)),
|
||||
/// Get document references for a specific position.
|
||||
GetReferences((ModuleSpecifier, u32)),
|
||||
}
|
||||
|
||||
impl RequestMethod {
|
||||
|
@ -1185,60 +1241,6 @@ impl RequestMethod {
|
|||
"method": "configure",
|
||||
"compilerOptions": config,
|
||||
}),
|
||||
RequestMethod::GetAsset(specifier) => json!({
|
||||
"id": id,
|
||||
"method": "getAsset",
|
||||
"specifier": specifier,
|
||||
}),
|
||||
RequestMethod::GetDiagnostics(specifiers) => json!({
|
||||
"id": id,
|
||||
"method": "getDiagnostics",
|
||||
"specifiers": specifiers,
|
||||
}),
|
||||
RequestMethod::GetQuickInfo((specifier, position)) => json!({
|
||||
"id": id,
|
||||
"method": "getQuickInfo",
|
||||
"specifier": specifier,
|
||||
"position": position,
|
||||
}),
|
||||
RequestMethod::GetDocumentHighlights((
|
||||
specifier,
|
||||
position,
|
||||
files_to_search,
|
||||
)) => json!({
|
||||
"id": id,
|
||||
"method": "getDocumentHighlights",
|
||||
"specifier": specifier,
|
||||
"position": position,
|
||||
"filesToSearch": files_to_search,
|
||||
}),
|
||||
RequestMethod::GetReferences((specifier, position)) => json!({
|
||||
"id": id,
|
||||
"method": "getReferences",
|
||||
"specifier": specifier,
|
||||
"position": position,
|
||||
}),
|
||||
RequestMethod::GetDefinition((specifier, position)) => json!({
|
||||
"id": id,
|
||||
"method": "getDefinition",
|
||||
"specifier": specifier,
|
||||
"position": position,
|
||||
}),
|
||||
RequestMethod::GetCompletions((specifier, position, preferences)) => {
|
||||
json!({
|
||||
"id": id,
|
||||
"method": "getCompletions",
|
||||
"specifier": specifier,
|
||||
"position": position,
|
||||
"preferences": preferences,
|
||||
})
|
||||
}
|
||||
RequestMethod::GetImplementation((specifier, position)) => json!({
|
||||
"id": id,
|
||||
"method": "getImplementation",
|
||||
"specifier": specifier,
|
||||
"position": position,
|
||||
}),
|
||||
RequestMethod::FindRenameLocations((
|
||||
specifier,
|
||||
position,
|
||||
|
@ -1256,6 +1258,65 @@ impl RequestMethod {
|
|||
"providePrefixAndSuffixTextForRename": provide_prefix_and_suffix_text_for_rename
|
||||
})
|
||||
}
|
||||
RequestMethod::GetAsset(specifier) => json!({
|
||||
"id": id,
|
||||
"method": "getAsset",
|
||||
"specifier": specifier,
|
||||
}),
|
||||
RequestMethod::GetCompletions((specifier, position, preferences)) => {
|
||||
json!({
|
||||
"id": id,
|
||||
"method": "getCompletions",
|
||||
"specifier": specifier,
|
||||
"position": position,
|
||||
"preferences": preferences,
|
||||
})
|
||||
}
|
||||
RequestMethod::GetDefinition((specifier, position)) => json!({
|
||||
"id": id,
|
||||
"method": "getDefinition",
|
||||
"specifier": specifier,
|
||||
"position": position,
|
||||
}),
|
||||
RequestMethod::GetDiagnostics(specifiers) => json!({
|
||||
"id": id,
|
||||
"method": "getDiagnostics",
|
||||
"specifiers": specifiers,
|
||||
}),
|
||||
RequestMethod::GetDocumentHighlights((
|
||||
specifier,
|
||||
position,
|
||||
files_to_search,
|
||||
)) => json!({
|
||||
"id": id,
|
||||
"method": "getDocumentHighlights",
|
||||
"specifier": specifier,
|
||||
"position": position,
|
||||
"filesToSearch": files_to_search,
|
||||
}),
|
||||
RequestMethod::GetImplementation((specifier, position)) => json!({
|
||||
"id": id,
|
||||
"method": "getImplementation",
|
||||
"specifier": specifier,
|
||||
"position": position,
|
||||
}),
|
||||
RequestMethod::GetNavigationTree(specifier) => json!({
|
||||
"id": id,
|
||||
"method": "getNavigationTree",
|
||||
"specifier": specifier,
|
||||
}),
|
||||
RequestMethod::GetQuickInfo((specifier, position)) => json!({
|
||||
"id": id,
|
||||
"method": "getQuickInfo",
|
||||
"specifier": specifier,
|
||||
"position": position,
|
||||
}),
|
||||
RequestMethod::GetReferences((specifier, position)) => json!({
|
||||
"id": id,
|
||||
"method": "getReferences",
|
||||
"specifier": specifier,
|
||||
"position": position,
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
10
cli/tests/lsp/code_lens_request.json
Normal file
10
cli/tests/lsp/code_lens_request.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "textDocument/codeLens",
|
||||
"params": {
|
||||
"textDocument": {
|
||||
"uri": "file:///a/file.ts"
|
||||
}
|
||||
}
|
||||
}
|
21
cli/tests/lsp/code_lens_resolve_request.json
Normal file
21
cli/tests/lsp/code_lens_resolve_request.json
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 4,
|
||||
"method": "codeLens/resolve",
|
||||
"params": {
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 6
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 7
|
||||
}
|
||||
},
|
||||
"data": {
|
||||
"specifier": "file:///a/file.ts",
|
||||
"source": "references"
|
||||
}
|
||||
}
|
||||
}
|
12
cli/tests/lsp/did_open_notification_cl_references.json
Normal file
12
cli/tests/lsp/did_open_notification_cl_references.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": "class A {\n a = \"a\";\n\n b() {\n console.log(this.a);\n }\n\n c() {\n this.a = \"c\";\n }\n}\n\nconst a = new A();\na.b();\n"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,9 @@
|
|||
"rootUri": null,
|
||||
"initializationOptions": {
|
||||
"enable": true,
|
||||
"codeLens": {
|
||||
"references": true
|
||||
},
|
||||
"lint": true,
|
||||
"importMap": null,
|
||||
"unstable": false
|
||||
|
|
|
@ -502,6 +502,18 @@ delete Object.prototype.__proto__;
|
|||
compilationSettings = options;
|
||||
return respond(id, true);
|
||||
}
|
||||
case "findRenameLocations": {
|
||||
return respond(
|
||||
id,
|
||||
languageService.findRenameLocations(
|
||||
request.specifier,
|
||||
request.position,
|
||||
request.findInStrings,
|
||||
request.findInComments,
|
||||
request.providePrefixAndSuffixTextForRename,
|
||||
),
|
||||
);
|
||||
}
|
||||
case "getAsset": {
|
||||
const sourceFile = host.getSourceFile(
|
||||
request.specifier,
|
||||
|
@ -509,6 +521,25 @@ delete Object.prototype.__proto__;
|
|||
);
|
||||
return respond(id, sourceFile && sourceFile.text);
|
||||
}
|
||||
case "getCompletions": {
|
||||
return respond(
|
||||
id,
|
||||
languageService.getCompletionsAtPosition(
|
||||
request.specifier,
|
||||
request.position,
|
||||
request.preferences,
|
||||
),
|
||||
);
|
||||
}
|
||||
case "getDefinition": {
|
||||
return respond(
|
||||
id,
|
||||
languageService.getDefinitionAndBoundSpan(
|
||||
request.specifier,
|
||||
request.position,
|
||||
),
|
||||
);
|
||||
}
|
||||
case "getDiagnostics": {
|
||||
try {
|
||||
/** @type {Record<string, any[]>} */
|
||||
|
@ -530,25 +561,6 @@ delete Object.prototype.__proto__;
|
|||
return respond(id, {});
|
||||
}
|
||||
}
|
||||
case "getQuickInfo": {
|
||||
return respond(
|
||||
id,
|
||||
languageService.getQuickInfoAtPosition(
|
||||
request.specifier,
|
||||
request.position,
|
||||
),
|
||||
);
|
||||
}
|
||||
case "getCompletions": {
|
||||
return respond(
|
||||
id,
|
||||
languageService.getCompletionsAtPosition(
|
||||
request.specifier,
|
||||
request.position,
|
||||
request.preferences,
|
||||
),
|
||||
);
|
||||
}
|
||||
case "getDocumentHighlights": {
|
||||
return respond(
|
||||
id,
|
||||
|
@ -559,24 +571,6 @@ delete Object.prototype.__proto__;
|
|||
),
|
||||
);
|
||||
}
|
||||
case "getReferences": {
|
||||
return respond(
|
||||
id,
|
||||
languageService.getReferencesAtPosition(
|
||||
request.specifier,
|
||||
request.position,
|
||||
),
|
||||
);
|
||||
}
|
||||
case "getDefinition": {
|
||||
return respond(
|
||||
id,
|
||||
languageService.getDefinitionAndBoundSpan(
|
||||
request.specifier,
|
||||
request.position,
|
||||
),
|
||||
);
|
||||
}
|
||||
case "getImplementation": {
|
||||
return respond(
|
||||
id,
|
||||
|
@ -586,15 +580,27 @@ delete Object.prototype.__proto__;
|
|||
),
|
||||
);
|
||||
}
|
||||
case "findRenameLocations": {
|
||||
case "getNavigationTree": {
|
||||
return respond(
|
||||
id,
|
||||
languageService.findRenameLocations(
|
||||
languageService.getNavigationTree(request.specifier),
|
||||
);
|
||||
}
|
||||
case "getQuickInfo": {
|
||||
return respond(
|
||||
id,
|
||||
languageService.getQuickInfoAtPosition(
|
||||
request.specifier,
|
||||
request.position,
|
||||
),
|
||||
);
|
||||
}
|
||||
case "getReferences": {
|
||||
return respond(
|
||||
id,
|
||||
languageService.getReferencesAtPosition(
|
||||
request.specifier,
|
||||
request.position,
|
||||
request.findInStrings,
|
||||
request.findInComments,
|
||||
request.providePrefixAndSuffixTextForRename,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
70
cli/tsc/compiler.d.ts
vendored
70
cli/tsc/compiler.d.ts
vendored
|
@ -42,15 +42,16 @@ declare global {
|
|||
|
||||
type LanguageServerRequest =
|
||||
| ConfigureRequest
|
||||
| FindRenameLocationsRequest
|
||||
| GetAsset
|
||||
| GetDiagnosticsRequest
|
||||
| GetQuickInfoRequest
|
||||
| GetDocumentHighlightsRequest
|
||||
| GetReferencesRequest
|
||||
| GetDefinitionRequest
|
||||
| GetCompletionsRequest
|
||||
| GetDefinitionRequest
|
||||
| GetDiagnosticsRequest
|
||||
| GetDocumentHighlightsRequest
|
||||
| GetImplementationRequest
|
||||
| FindRenameLocationsRequest;
|
||||
| GetNavigationTree
|
||||
| GetQuickInfoRequest
|
||||
| GetReferencesRequest;
|
||||
|
||||
interface BaseLanguageServerRequest {
|
||||
id: number;
|
||||
|
@ -63,18 +64,34 @@ declare global {
|
|||
compilerOptions: Record<string, any>;
|
||||
}
|
||||
|
||||
interface FindRenameLocationsRequest extends BaseLanguageServerRequest {
|
||||
method: "findRenameLocations";
|
||||
specifier: string;
|
||||
position: number;
|
||||
findInStrings: boolean;
|
||||
findInComments: boolean;
|
||||
providePrefixAndSuffixTextForRename: boolean;
|
||||
}
|
||||
|
||||
interface GetAsset extends BaseLanguageServerRequest {
|
||||
method: "getAsset";
|
||||
specifier: string;
|
||||
}
|
||||
|
||||
interface GetCompletionsRequest extends BaseLanguageServerRequest {
|
||||
method: "getCompletions";
|
||||
specifier: string;
|
||||
position: number;
|
||||
preferences: ts.UserPreferences;
|
||||
}
|
||||
|
||||
interface GetDiagnosticsRequest extends BaseLanguageServerRequest {
|
||||
method: "getDiagnostics";
|
||||
specifiers: string[];
|
||||
}
|
||||
|
||||
interface GetQuickInfoRequest extends BaseLanguageServerRequest {
|
||||
method: "getQuickInfo";
|
||||
interface GetDefinitionRequest extends BaseLanguageServerRequest {
|
||||
method: "getDefinition";
|
||||
specifier: string;
|
||||
position: number;
|
||||
}
|
||||
|
@ -86,37 +103,26 @@ declare global {
|
|||
filesToSearch: string[];
|
||||
}
|
||||
|
||||
interface GetReferencesRequest extends BaseLanguageServerRequest {
|
||||
method: "getReferences";
|
||||
specifier: string;
|
||||
position: number;
|
||||
}
|
||||
|
||||
interface GetDefinitionRequest extends BaseLanguageServerRequest {
|
||||
method: "getDefinition";
|
||||
specifier: string;
|
||||
position: number;
|
||||
}
|
||||
|
||||
interface GetCompletionsRequest extends BaseLanguageServerRequest {
|
||||
method: "getCompletions";
|
||||
specifier: string;
|
||||
position: number;
|
||||
preferences: ts.UserPreferences;
|
||||
}
|
||||
|
||||
interface GetImplementationRequest extends BaseLanguageServerRequest {
|
||||
method: "getImplementation";
|
||||
specifier: string;
|
||||
position: number;
|
||||
}
|
||||
|
||||
interface FindRenameLocationsRequest extends BaseLanguageServerRequest {
|
||||
method: "findRenameLocations";
|
||||
interface GetNavigationTree extends BaseLanguageServerRequest {
|
||||
method: "getNavigationTree";
|
||||
specifier: string;
|
||||
}
|
||||
|
||||
interface GetQuickInfoRequest extends BaseLanguageServerRequest {
|
||||
method: "getQuickInfo";
|
||||
specifier: string;
|
||||
position: number;
|
||||
}
|
||||
|
||||
interface GetReferencesRequest extends BaseLanguageServerRequest {
|
||||
method: "getReferences";
|
||||
specifier: string;
|
||||
position: number;
|
||||
findInStrings: boolean;
|
||||
findInComments: boolean;
|
||||
providePrefixAndSuffixTextForRename: boolean;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
|
||||
|
||||
use crate::normalize_path;
|
||||
use serde::de;
|
||||
use serde::Deserialize;
|
||||
use serde::Deserializer;
|
||||
use std::env::current_dir;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
|
@ -209,9 +212,21 @@ impl PartialEq<String> for ModuleSpecifier {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ModuleSpecifier {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
let url_str: String = Deserialize::deserialize(deserializer)?;
|
||||
ModuleSpecifier::resolve_url(&url_str).map_err(de::Error::custom)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::serde_json::from_value;
|
||||
use crate::serde_json::json;
|
||||
use std::path::Path;
|
||||
|
||||
#[test]
|
||||
|
@ -545,4 +560,13 @@ mod tests {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_module_specifier() {
|
||||
let actual: ModuleSpecifier =
|
||||
from_value(json!("http://deno.land/x/mod.ts")).unwrap();
|
||||
let expected =
|
||||
ModuleSpecifier::resolve_url("http://deno.land/x/mod.ts").unwrap();
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue