mirror of
https://github.com/denoland/deno.git
synced 2025-08-04 19:08:15 +00: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
248
cli/lsp/tsc.rs
248
cli/lsp/tsc.rs
|
@ -35,10 +35,10 @@ use log::warn;
|
|||
use lspower::lsp;
|
||||
use regex::Captures;
|
||||
use regex::Regex;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::thread;
|
||||
use std::{borrow::Cow, cmp};
|
||||
use std::{collections::HashMap, path::Path};
|
||||
use text_size::TextSize;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::oneshot;
|
||||
|
@ -283,6 +283,13 @@ fn parse_kind_modifier(kind_modifiers: &str) -> HashSet<&str> {
|
|||
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)]
|
||||
pub enum ScriptElementKind {
|
||||
#[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)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
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)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CompletionEntryDetails {
|
||||
|
@ -1956,6 +2166,12 @@ pub enum RequestMethod {
|
|||
GetSmartSelectionRange((ModuleSpecifier, u32)),
|
||||
/// Get the diagnostic codes that support some form of code fix.
|
||||
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 {
|
||||
|
@ -2092,6 +2308,36 @@ impl RequestMethod {
|
|||
"id": id,
|
||||
"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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue