mirror of
https://github.com/denoland/deno.git
synced 2024-11-22 15:06:54 -05:00
feat(lsp): implement textDocument/prepareCallHierarchy (#10061)
This commit is contained in:
parent
0552eaf569
commit
65a2a04d3b
9 changed files with 747 additions and 3 deletions
|
@ -5,6 +5,7 @@
|
||||||
///! language server, which helps determine what messages are sent from the
|
///! language server, which helps determine what messages are sent from the
|
||||||
///! client.
|
///! client.
|
||||||
///!
|
///!
|
||||||
|
use lspower::lsp::CallHierarchyServerCapability;
|
||||||
use lspower::lsp::ClientCapabilities;
|
use lspower::lsp::ClientCapabilities;
|
||||||
use lspower::lsp::CodeActionKind;
|
use lspower::lsp::CodeActionKind;
|
||||||
use lspower::lsp::CodeActionOptions;
|
use lspower::lsp::CodeActionOptions;
|
||||||
|
@ -114,7 +115,7 @@ pub fn server_capabilities(
|
||||||
document_link_provider: None,
|
document_link_provider: None,
|
||||||
color_provider: None,
|
color_provider: None,
|
||||||
execute_command_provider: None,
|
execute_command_provider: None,
|
||||||
call_hierarchy_provider: None,
|
call_hierarchy_provider: Some(CallHierarchyServerCapability::Simple(true)),
|
||||||
semantic_tokens_provider: None,
|
semantic_tokens_provider: None,
|
||||||
workspace: None,
|
workspace: None,
|
||||||
experimental: None,
|
experimental: None,
|
||||||
|
|
|
@ -1643,6 +1643,192 @@ impl Inner {
|
||||||
Ok(response)
|
Ok(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn incoming_calls(
|
||||||
|
&mut self,
|
||||||
|
params: CallHierarchyIncomingCallsParams,
|
||||||
|
) -> LspResult<Option<Vec<CallHierarchyIncomingCall>>> {
|
||||||
|
if !self.enabled() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let mark = self.performance.mark("incoming_calls");
|
||||||
|
let specifier = self.url_map.normalize_url(¶ms.item.uri);
|
||||||
|
|
||||||
|
let line_index =
|
||||||
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
||||||
|
line_index
|
||||||
|
} else {
|
||||||
|
return Err(LspError::invalid_params(format!(
|
||||||
|
"An unexpected specifier ({}) was provided.",
|
||||||
|
specifier
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
let req = tsc::RequestMethod::ProvideCallHierarchyIncomingCalls((
|
||||||
|
specifier.clone(),
|
||||||
|
line_index.offset_tsc(params.item.selection_range.start)?,
|
||||||
|
));
|
||||||
|
let incoming_calls: Vec<tsc::CallHierarchyIncomingCall> = self
|
||||||
|
.ts_server
|
||||||
|
.request(self.snapshot(), req)
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
error!("Failed to request to tsserver {}", err);
|
||||||
|
LspError::invalid_request()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let maybe_root_path_owned = self
|
||||||
|
.config
|
||||||
|
.root_uri
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|uri| uri.to_file_path().ok());
|
||||||
|
let mut resolved_items = Vec::<CallHierarchyIncomingCall>::new();
|
||||||
|
for item in incoming_calls.iter() {
|
||||||
|
if let Some(resolved) = item
|
||||||
|
.try_resolve_call_hierarchy_incoming_call(
|
||||||
|
self,
|
||||||
|
maybe_root_path_owned.as_deref(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
resolved_items.push(resolved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.performance.measure(mark);
|
||||||
|
Ok(Some(resolved_items))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn outgoing_calls(
|
||||||
|
&mut self,
|
||||||
|
params: CallHierarchyOutgoingCallsParams,
|
||||||
|
) -> LspResult<Option<Vec<CallHierarchyOutgoingCall>>> {
|
||||||
|
if !self.enabled() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let mark = self.performance.mark("outgoing_calls");
|
||||||
|
let specifier = self.url_map.normalize_url(¶ms.item.uri);
|
||||||
|
|
||||||
|
let line_index =
|
||||||
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
||||||
|
line_index
|
||||||
|
} else {
|
||||||
|
return Err(LspError::invalid_params(format!(
|
||||||
|
"An unexpected specifier ({}) was provided.",
|
||||||
|
specifier
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
let req = tsc::RequestMethod::ProvideCallHierarchyOutgoingCalls((
|
||||||
|
specifier.clone(),
|
||||||
|
line_index.offset_tsc(params.item.selection_range.start)?,
|
||||||
|
));
|
||||||
|
let outgoing_calls: Vec<tsc::CallHierarchyOutgoingCall> = self
|
||||||
|
.ts_server
|
||||||
|
.request(self.snapshot(), req)
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
error!("Failed to request to tsserver {}", err);
|
||||||
|
LspError::invalid_request()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let maybe_root_path_owned = self
|
||||||
|
.config
|
||||||
|
.root_uri
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|uri| uri.to_file_path().ok());
|
||||||
|
let mut resolved_items = Vec::<CallHierarchyOutgoingCall>::new();
|
||||||
|
for item in outgoing_calls.iter() {
|
||||||
|
if let Some(resolved) = item
|
||||||
|
.try_resolve_call_hierarchy_outgoing_call(
|
||||||
|
&line_index,
|
||||||
|
self,
|
||||||
|
maybe_root_path_owned.as_deref(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
resolved_items.push(resolved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.performance.measure(mark);
|
||||||
|
Ok(Some(resolved_items))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn prepare_call_hierarchy(
|
||||||
|
&mut self,
|
||||||
|
params: CallHierarchyPrepareParams,
|
||||||
|
) -> LspResult<Option<Vec<CallHierarchyItem>>> {
|
||||||
|
if !self.enabled() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
let mark = self.performance.mark("prepare_call_hierarchy");
|
||||||
|
let specifier = self
|
||||||
|
.url_map
|
||||||
|
.normalize_url(¶ms.text_document_position_params.text_document.uri);
|
||||||
|
|
||||||
|
let line_index =
|
||||||
|
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
||||||
|
line_index
|
||||||
|
} else {
|
||||||
|
return Err(LspError::invalid_params(format!(
|
||||||
|
"An unexpected specifier ({}) was provided.",
|
||||||
|
specifier
|
||||||
|
)));
|
||||||
|
};
|
||||||
|
|
||||||
|
let req = tsc::RequestMethod::PrepareCallHierarchy((
|
||||||
|
specifier.clone(),
|
||||||
|
line_index.offset_tsc(params.text_document_position_params.position)?,
|
||||||
|
));
|
||||||
|
let maybe_one_or_many: Option<tsc::OneOrMany<tsc::CallHierarchyItem>> =
|
||||||
|
self
|
||||||
|
.ts_server
|
||||||
|
.request(self.snapshot(), req)
|
||||||
|
.await
|
||||||
|
.map_err(|err| {
|
||||||
|
error!("Failed to request to tsserver {}", err);
|
||||||
|
LspError::invalid_request()
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let response = if let Some(one_or_many) = maybe_one_or_many {
|
||||||
|
let maybe_root_path_owned = self
|
||||||
|
.config
|
||||||
|
.root_uri
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|uri| uri.to_file_path().ok());
|
||||||
|
let mut resolved_items = Vec::<CallHierarchyItem>::new();
|
||||||
|
match one_or_many {
|
||||||
|
tsc::OneOrMany::One(item) => {
|
||||||
|
if let Some(resolved) = item
|
||||||
|
.try_resolve_call_hierarchy_item(
|
||||||
|
self,
|
||||||
|
maybe_root_path_owned.as_deref(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
resolved_items.push(resolved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tsc::OneOrMany::Many(items) => {
|
||||||
|
for item in items.iter() {
|
||||||
|
if let Some(resolved) = item
|
||||||
|
.try_resolve_call_hierarchy_item(
|
||||||
|
self,
|
||||||
|
maybe_root_path_owned.as_deref(),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
resolved_items.push(resolved);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(resolved_items)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
self.performance.measure(mark);
|
||||||
|
Ok(response)
|
||||||
|
}
|
||||||
|
|
||||||
async fn rename(
|
async fn rename(
|
||||||
&mut self,
|
&mut self,
|
||||||
params: RenameParams,
|
params: RenameParams,
|
||||||
|
@ -1971,6 +2157,27 @@ impl lspower::LanguageServer for LanguageServer {
|
||||||
self.0.lock().await.folding_range(params).await
|
self.0.lock().await.folding_range(params).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn incoming_calls(
|
||||||
|
&self,
|
||||||
|
params: CallHierarchyIncomingCallsParams,
|
||||||
|
) -> LspResult<Option<Vec<CallHierarchyIncomingCall>>> {
|
||||||
|
self.0.lock().await.incoming_calls(params).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn outgoing_calls(
|
||||||
|
&self,
|
||||||
|
params: CallHierarchyOutgoingCallsParams,
|
||||||
|
) -> LspResult<Option<Vec<CallHierarchyOutgoingCall>>> {
|
||||||
|
self.0.lock().await.outgoing_calls(params).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn prepare_call_hierarchy(
|
||||||
|
&self,
|
||||||
|
params: CallHierarchyPrepareParams,
|
||||||
|
) -> LspResult<Option<Vec<CallHierarchyItem>>> {
|
||||||
|
self.0.lock().await.prepare_call_hierarchy(params).await
|
||||||
|
}
|
||||||
|
|
||||||
async fn rename(
|
async fn rename(
|
||||||
&self,
|
&self,
|
||||||
params: RenameParams,
|
params: RenameParams,
|
||||||
|
@ -2471,6 +2678,154 @@ mod tests {
|
||||||
harness.run().await;
|
harness.run().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_call_hierarchy() {
|
||||||
|
let mut harness = LspTestHarness::new(vec![
|
||||||
|
("initialize_request.json", LspResponse::RequestAny),
|
||||||
|
("initialized_notification.json", LspResponse::None),
|
||||||
|
(
|
||||||
|
"prepare_call_hierarchy_did_open_notification.json",
|
||||||
|
LspResponse::None,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"prepare_call_hierarchy_request.json",
|
||||||
|
LspResponse::Request(
|
||||||
|
2,
|
||||||
|
json!([
|
||||||
|
{
|
||||||
|
"name": "baz",
|
||||||
|
"kind": 6,
|
||||||
|
"detail": "Bar",
|
||||||
|
"uri": "file:///a/file.ts",
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 5,
|
||||||
|
"character": 2
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 7,
|
||||||
|
"character": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"selectionRange": {
|
||||||
|
"start": {
|
||||||
|
"line": 5,
|
||||||
|
"character": 2
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 5,
|
||||||
|
"character": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"incoming_calls_request.json",
|
||||||
|
LspResponse::Request(
|
||||||
|
4,
|
||||||
|
json!([
|
||||||
|
{
|
||||||
|
"from": {
|
||||||
|
"name": "main",
|
||||||
|
"kind": 12,
|
||||||
|
"detail": "",
|
||||||
|
"uri": "file:///a/file.ts",
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 10,
|
||||||
|
"character": 0
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 13,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"selectionRange": {
|
||||||
|
"start": {
|
||||||
|
"line": 10,
|
||||||
|
"character": 9
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 10,
|
||||||
|
"character": 13
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fromRanges": [
|
||||||
|
{
|
||||||
|
"start": {
|
||||||
|
"line": 12,
|
||||||
|
"character": 6
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 12,
|
||||||
|
"character": 9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"outgoing_calls_request.json",
|
||||||
|
LspResponse::Request(
|
||||||
|
5,
|
||||||
|
json!([
|
||||||
|
{
|
||||||
|
"to": {
|
||||||
|
"name": "foo",
|
||||||
|
"kind": 12,
|
||||||
|
"detail": "",
|
||||||
|
"uri": "file:///a/file.ts",
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 0,
|
||||||
|
"character": 0
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 2,
|
||||||
|
"character": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"selectionRange": {
|
||||||
|
"start": {
|
||||||
|
"line": 0,
|
||||||
|
"character": 9
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 0,
|
||||||
|
"character": 12
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"fromRanges": [
|
||||||
|
{
|
||||||
|
"start": {
|
||||||
|
"line": 6,
|
||||||
|
"character": 11
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 6,
|
||||||
|
"character": 14
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"shutdown_request.json",
|
||||||
|
LspResponse::Request(3, json!(null)),
|
||||||
|
),
|
||||||
|
("exit_notification.json", LspResponse::None),
|
||||||
|
]);
|
||||||
|
harness.run().await;
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_format_mbc() {
|
async fn test_format_mbc() {
|
||||||
let mut harness = LspTestHarness::new(vec![
|
let mut harness = LspTestHarness::new(vec![
|
||||||
|
|
248
cli/lsp/tsc.rs
248
cli/lsp/tsc.rs
|
@ -35,10 +35,10 @@ use log::warn;
|
||||||
use lspower::lsp;
|
use lspower::lsp;
|
||||||
use regex::Captures;
|
use regex::Captures;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::{borrow::Cow, cmp};
|
use std::{borrow::Cow, cmp};
|
||||||
|
use std::{collections::HashMap, path::Path};
|
||||||
use text_size::TextSize;
|
use text_size::TextSize;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::sync::oneshot;
|
use tokio::sync::oneshot;
|
||||||
|
@ -283,6 +283,13 @@ fn parse_kind_modifier(kind_modifiers: &str) -> HashSet<&str> {
|
||||||
re.split(kind_modifiers).collect()
|
re.split(kind_modifiers).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum OneOrMany<T> {
|
||||||
|
One(T),
|
||||||
|
Many(Vec<T>),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
pub enum ScriptElementKind {
|
pub enum ScriptElementKind {
|
||||||
#[serde(rename = "")]
|
#[serde(rename = "")]
|
||||||
|
@ -411,6 +418,33 @@ impl From<ScriptElementKind> for lsp::CompletionItemKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<ScriptElementKind> for lsp::SymbolKind {
|
||||||
|
fn from(kind: ScriptElementKind) -> Self {
|
||||||
|
match kind {
|
||||||
|
ScriptElementKind::ModuleElement => lsp::SymbolKind::Module,
|
||||||
|
ScriptElementKind::ClassElement => lsp::SymbolKind::Class,
|
||||||
|
ScriptElementKind::EnumElement => lsp::SymbolKind::Enum,
|
||||||
|
ScriptElementKind::InterfaceElement => lsp::SymbolKind::Interface,
|
||||||
|
ScriptElementKind::MemberFunctionElement => lsp::SymbolKind::Method,
|
||||||
|
ScriptElementKind::MemberVariableElement => lsp::SymbolKind::Property,
|
||||||
|
ScriptElementKind::MemberGetAccessorElement => lsp::SymbolKind::Property,
|
||||||
|
ScriptElementKind::MemberSetAccessorElement => lsp::SymbolKind::Property,
|
||||||
|
ScriptElementKind::VariableElement => lsp::SymbolKind::Variable,
|
||||||
|
ScriptElementKind::ConstElement => lsp::SymbolKind::Variable,
|
||||||
|
ScriptElementKind::LocalVariableElement => lsp::SymbolKind::Variable,
|
||||||
|
ScriptElementKind::FunctionElement => lsp::SymbolKind::Function,
|
||||||
|
ScriptElementKind::LocalFunctionElement => lsp::SymbolKind::Function,
|
||||||
|
ScriptElementKind::ConstructSignatureElement => {
|
||||||
|
lsp::SymbolKind::Constructor
|
||||||
|
}
|
||||||
|
ScriptElementKind::ConstructorImplementationElement => {
|
||||||
|
lsp::SymbolKind::Constructor
|
||||||
|
}
|
||||||
|
_ => lsp::SymbolKind::Variable,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct TextSpan {
|
pub struct TextSpan {
|
||||||
|
@ -917,6 +951,182 @@ impl ReferenceEntry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CallHierarchyItem {
|
||||||
|
name: String,
|
||||||
|
kind: ScriptElementKind,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
kind_modifiers: Option<String>,
|
||||||
|
file: String,
|
||||||
|
span: TextSpan,
|
||||||
|
selection_span: TextSpan,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
container_name: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CallHierarchyItem {
|
||||||
|
pub(crate) async fn try_resolve_call_hierarchy_item(
|
||||||
|
&self,
|
||||||
|
language_server: &mut language_server::Inner,
|
||||||
|
maybe_root_path: Option<&Path>,
|
||||||
|
) -> Option<lsp::CallHierarchyItem> {
|
||||||
|
let target_specifier = resolve_url(&self.file).unwrap();
|
||||||
|
let target_line_index = language_server
|
||||||
|
.get_line_index(target_specifier)
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
Some(self.to_call_hierarchy_item(
|
||||||
|
&target_line_index,
|
||||||
|
language_server,
|
||||||
|
maybe_root_path,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn to_call_hierarchy_item(
|
||||||
|
&self,
|
||||||
|
line_index: &LineIndex,
|
||||||
|
language_server: &mut language_server::Inner,
|
||||||
|
maybe_root_path: Option<&Path>,
|
||||||
|
) -> lsp::CallHierarchyItem {
|
||||||
|
let target_specifier = resolve_url(&self.file).unwrap();
|
||||||
|
let uri = language_server
|
||||||
|
.url_map
|
||||||
|
.normalize_specifier(&target_specifier)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let use_file_name = self.is_source_file_item();
|
||||||
|
let maybe_file_path = if uri.scheme() == "file" {
|
||||||
|
uri.to_file_path().ok()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
let name = if use_file_name {
|
||||||
|
if let Some(file_path) = maybe_file_path.as_ref() {
|
||||||
|
file_path.file_name().unwrap().to_string_lossy().to_string()
|
||||||
|
} else {
|
||||||
|
uri.to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.name.clone()
|
||||||
|
};
|
||||||
|
let detail = if use_file_name {
|
||||||
|
if let Some(file_path) = maybe_file_path.as_ref() {
|
||||||
|
// TODO: update this to work with multi root workspaces
|
||||||
|
let parent_dir = file_path.parent().unwrap();
|
||||||
|
if let Some(root_path) = maybe_root_path {
|
||||||
|
parent_dir
|
||||||
|
.strip_prefix(root_path)
|
||||||
|
.unwrap_or(parent_dir)
|
||||||
|
.to_string_lossy()
|
||||||
|
.to_string()
|
||||||
|
} else {
|
||||||
|
parent_dir.to_string_lossy().to_string()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.container_name.as_ref().cloned().unwrap_or_default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut tags: Option<Vec<lsp::SymbolTag>> = None;
|
||||||
|
if let Some(modifiers) = self.kind_modifiers.as_ref() {
|
||||||
|
let kind_modifiers = parse_kind_modifier(modifiers);
|
||||||
|
if kind_modifiers.contains("deprecated") {
|
||||||
|
tags = Some(vec![lsp::SymbolTag::Deprecated]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lsp::CallHierarchyItem {
|
||||||
|
name,
|
||||||
|
tags,
|
||||||
|
uri,
|
||||||
|
detail: Some(detail),
|
||||||
|
kind: self.kind.clone().into(),
|
||||||
|
range: self.span.to_range(line_index),
|
||||||
|
selection_range: self.selection_span.to_range(line_index),
|
||||||
|
data: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_source_file_item(&self) -> bool {
|
||||||
|
self.kind == ScriptElementKind::ScriptElement
|
||||||
|
|| self.kind == ScriptElementKind::ModuleElement
|
||||||
|
&& self.selection_span.start == 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CallHierarchyIncomingCall {
|
||||||
|
from: CallHierarchyItem,
|
||||||
|
from_spans: Vec<TextSpan>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CallHierarchyIncomingCall {
|
||||||
|
pub(crate) async fn try_resolve_call_hierarchy_incoming_call(
|
||||||
|
&self,
|
||||||
|
language_server: &mut language_server::Inner,
|
||||||
|
maybe_root_path: Option<&Path>,
|
||||||
|
) -> Option<lsp::CallHierarchyIncomingCall> {
|
||||||
|
let target_specifier = resolve_url(&self.from.file).unwrap();
|
||||||
|
let target_line_index = language_server
|
||||||
|
.get_line_index(target_specifier)
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
Some(lsp::CallHierarchyIncomingCall {
|
||||||
|
from: self.from.to_call_hierarchy_item(
|
||||||
|
&target_line_index,
|
||||||
|
language_server,
|
||||||
|
maybe_root_path,
|
||||||
|
),
|
||||||
|
from_ranges: self
|
||||||
|
.from_spans
|
||||||
|
.iter()
|
||||||
|
.map(|span| span.to_range(&target_line_index))
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct CallHierarchyOutgoingCall {
|
||||||
|
to: CallHierarchyItem,
|
||||||
|
from_spans: Vec<TextSpan>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CallHierarchyOutgoingCall {
|
||||||
|
pub(crate) async fn try_resolve_call_hierarchy_outgoing_call(
|
||||||
|
&self,
|
||||||
|
line_index: &LineIndex,
|
||||||
|
language_server: &mut language_server::Inner,
|
||||||
|
maybe_root_path: Option<&Path>,
|
||||||
|
) -> Option<lsp::CallHierarchyOutgoingCall> {
|
||||||
|
let target_specifier = resolve_url(&self.to.file).unwrap();
|
||||||
|
let target_line_index = language_server
|
||||||
|
.get_line_index(target_specifier)
|
||||||
|
.await
|
||||||
|
.ok()?;
|
||||||
|
|
||||||
|
Some(lsp::CallHierarchyOutgoingCall {
|
||||||
|
to: self.to.to_call_hierarchy_item(
|
||||||
|
&target_line_index,
|
||||||
|
language_server,
|
||||||
|
maybe_root_path,
|
||||||
|
),
|
||||||
|
from_ranges: self
|
||||||
|
.from_spans
|
||||||
|
.iter()
|
||||||
|
.map(|span| span.to_range(&line_index))
|
||||||
|
.collect(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CompletionEntryDetails {
|
pub struct CompletionEntryDetails {
|
||||||
|
@ -1956,6 +2166,12 @@ pub enum RequestMethod {
|
||||||
GetSmartSelectionRange((ModuleSpecifier, u32)),
|
GetSmartSelectionRange((ModuleSpecifier, u32)),
|
||||||
/// Get the diagnostic codes that support some form of code fix.
|
/// Get the diagnostic codes that support some form of code fix.
|
||||||
GetSupportedCodeFixes,
|
GetSupportedCodeFixes,
|
||||||
|
/// Resolve a call hierarchy item for a specific position.
|
||||||
|
PrepareCallHierarchy((ModuleSpecifier, u32)),
|
||||||
|
/// Resolve incoming call hierarchy items for a specific position.
|
||||||
|
ProvideCallHierarchyIncomingCalls((ModuleSpecifier, u32)),
|
||||||
|
/// Resolve outgoing call hierarchy items for a specific position.
|
||||||
|
ProvideCallHierarchyOutgoingCalls((ModuleSpecifier, u32)),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RequestMethod {
|
impl RequestMethod {
|
||||||
|
@ -2092,6 +2308,36 @@ impl RequestMethod {
|
||||||
"id": id,
|
"id": id,
|
||||||
"method": "getSupportedCodeFixes",
|
"method": "getSupportedCodeFixes",
|
||||||
}),
|
}),
|
||||||
|
RequestMethod::PrepareCallHierarchy((specifier, position)) => {
|
||||||
|
json!({
|
||||||
|
"id": id,
|
||||||
|
"method": "prepareCallHierarchy",
|
||||||
|
"specifier": specifier,
|
||||||
|
"position": position
|
||||||
|
})
|
||||||
|
}
|
||||||
|
RequestMethod::ProvideCallHierarchyIncomingCalls((
|
||||||
|
specifier,
|
||||||
|
position,
|
||||||
|
)) => {
|
||||||
|
json!({
|
||||||
|
"id": id,
|
||||||
|
"method": "provideCallHierarchyIncomingCalls",
|
||||||
|
"specifier": specifier,
|
||||||
|
"position": position
|
||||||
|
})
|
||||||
|
}
|
||||||
|
RequestMethod::ProvideCallHierarchyOutgoingCalls((
|
||||||
|
specifier,
|
||||||
|
position,
|
||||||
|
)) => {
|
||||||
|
json!({
|
||||||
|
"id": id,
|
||||||
|
"method": "provideCallHierarchyOutgoingCalls",
|
||||||
|
"specifier": specifier,
|
||||||
|
"position": position
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
33
cli/tests/lsp/incoming_calls_request.json
Normal file
33
cli/tests/lsp/incoming_calls_request.json
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 4,
|
||||||
|
"method": "callHierarchy/incomingCalls",
|
||||||
|
"params": {
|
||||||
|
"item": {
|
||||||
|
"name": "baz",
|
||||||
|
"kind": 6,
|
||||||
|
"detail": "Bar",
|
||||||
|
"uri": "file:///a/file.ts",
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 5,
|
||||||
|
"character": 2
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 7,
|
||||||
|
"character": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"selectionRange": {
|
||||||
|
"start": {
|
||||||
|
"line": 5,
|
||||||
|
"character": 2
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 5,
|
||||||
|
"character": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
cli/tests/lsp/outgoing_calls_request.json
Normal file
33
cli/tests/lsp/outgoing_calls_request.json
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 5,
|
||||||
|
"method": "callHierarchy/outgoingCalls",
|
||||||
|
"params": {
|
||||||
|
"item": {
|
||||||
|
"name": "baz",
|
||||||
|
"kind": 6,
|
||||||
|
"detail": "Bar",
|
||||||
|
"uri": "file:///a/file.ts",
|
||||||
|
"range": {
|
||||||
|
"start": {
|
||||||
|
"line": 5,
|
||||||
|
"character": 2
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 7,
|
||||||
|
"character": 3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"selectionRange": {
|
||||||
|
"start": {
|
||||||
|
"line": 5,
|
||||||
|
"character": 2
|
||||||
|
},
|
||||||
|
"end": {
|
||||||
|
"line": 5,
|
||||||
|
"character": 5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"method": "textDocument/didOpen",
|
||||||
|
"params": {
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "file:///a/file.ts",
|
||||||
|
"languageId": "typescript",
|
||||||
|
"version": 1,
|
||||||
|
"text": "function foo() {\n return false;\n}\n\nclass Bar {\n baz() {\n return foo();\n }\n}\n\nfunction main() {\n const bar = new Bar();\n bar.baz();\n}\n\nmain();"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
14
cli/tests/lsp/prepare_call_hierarchy_request.json
Normal file
14
cli/tests/lsp/prepare_call_hierarchy_request.json
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": 2,
|
||||||
|
"method": "textDocument/prepareCallHierarchy",
|
||||||
|
"params": {
|
||||||
|
"textDocument": {
|
||||||
|
"uri": "file:///a/file.ts"
|
||||||
|
},
|
||||||
|
"position": {
|
||||||
|
"line": 5,
|
||||||
|
"character": 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -726,6 +726,33 @@ delete Object.prototype.__proto__;
|
||||||
ts.getSupportedCodeFixes(),
|
ts.getSupportedCodeFixes(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
case "prepareCallHierarchy": {
|
||||||
|
return respond(
|
||||||
|
id,
|
||||||
|
languageService.prepareCallHierarchy(
|
||||||
|
request.specifier,
|
||||||
|
request.position,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "provideCallHierarchyIncomingCalls": {
|
||||||
|
return respond(
|
||||||
|
id,
|
||||||
|
languageService.provideCallHierarchyIncomingCalls(
|
||||||
|
request.specifier,
|
||||||
|
request.position,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "provideCallHierarchyOutgoingCalls": {
|
||||||
|
return respond(
|
||||||
|
id,
|
||||||
|
languageService.provideCallHierarchyOutgoingCalls(
|
||||||
|
request.specifier,
|
||||||
|
request.position,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new TypeError(
|
throw new TypeError(
|
||||||
// @ts-ignore exhausted case statement sets type to never
|
// @ts-ignore exhausted case statement sets type to never
|
||||||
|
|
25
cli/tsc/compiler.d.ts
vendored
25
cli/tsc/compiler.d.ts
vendored
|
@ -63,7 +63,10 @@ declare global {
|
||||||
| GetReferencesRequest
|
| GetReferencesRequest
|
||||||
| GetSignatureHelpItemsRequest
|
| GetSignatureHelpItemsRequest
|
||||||
| GetSmartSelectionRange
|
| GetSmartSelectionRange
|
||||||
| GetSupportedCodeFixes;
|
| GetSupportedCodeFixes
|
||||||
|
| PrepareCallHierarchy
|
||||||
|
| ProvideCallHierarchyIncomingCalls
|
||||||
|
| ProvideCallHierarchyOutgoingCalls;
|
||||||
|
|
||||||
interface BaseLanguageServerRequest {
|
interface BaseLanguageServerRequest {
|
||||||
id: number;
|
id: number;
|
||||||
|
@ -185,4 +188,24 @@ declare global {
|
||||||
interface GetSupportedCodeFixes extends BaseLanguageServerRequest {
|
interface GetSupportedCodeFixes extends BaseLanguageServerRequest {
|
||||||
method: "getSupportedCodeFixes";
|
method: "getSupportedCodeFixes";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface PrepareCallHierarchy extends BaseLanguageServerRequest {
|
||||||
|
method: "prepareCallHierarchy";
|
||||||
|
specifier: string;
|
||||||
|
position: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProvideCallHierarchyIncomingCalls
|
||||||
|
extends BaseLanguageServerRequest {
|
||||||
|
method: "provideCallHierarchyIncomingCalls";
|
||||||
|
specifier: string;
|
||||||
|
position: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProvideCallHierarchyOutgoingCalls
|
||||||
|
extends BaseLanguageServerRequest {
|
||||||
|
method: "provideCallHierarchyOutgoingCalls";
|
||||||
|
specifier: string;
|
||||||
|
position: number;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue