mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
Implement proposed CallHierarchy feature
See: https://github.com/microsoft/vscode-languageserver-node/blob/master/protocol/src/protocol.callHierarchy.proposed.ts
This commit is contained in:
parent
928ecd069a
commit
1b19a8aa5e
11 changed files with 502 additions and 15 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1032,6 +1032,7 @@ dependencies = [
|
||||||
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"format-buf 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"format-buf 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"fst 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
"fst 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"insta 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"insta 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
"join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
|
|
@ -13,6 +13,7 @@ wasm = []
|
||||||
[dependencies]
|
[dependencies]
|
||||||
either = "1.5"
|
either = "1.5"
|
||||||
format-buf = "1.0.0"
|
format-buf = "1.0.0"
|
||||||
|
indexmap = "1.3.0"
|
||||||
itertools = "0.8.0"
|
itertools = "0.8.0"
|
||||||
join_to_string = "0.1.3"
|
join_to_string = "0.1.3"
|
||||||
log = "0.4.5"
|
log = "0.4.5"
|
||||||
|
|
337
crates/ra_ide/src/call_hierarchy.rs
Normal file
337
crates/ra_ide/src/call_hierarchy.rs
Normal file
|
@ -0,0 +1,337 @@
|
||||||
|
//! Entry point for call-hierarchy
|
||||||
|
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
|
||||||
|
use hir::db::AstDatabase;
|
||||||
|
use ra_syntax::{
|
||||||
|
ast::{self, DocCommentsOwner},
|
||||||
|
match_ast, AstNode, TextRange,
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
call_info::FnCallNode,
|
||||||
|
db::RootDatabase,
|
||||||
|
display::{ShortLabel, ToNav},
|
||||||
|
expand::descend_into_macros,
|
||||||
|
goto_definition, references, FilePosition, NavigationTarget, RangeInfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct CallLocations {
|
||||||
|
funcs: IndexMap<NavigationTarget, Vec<TextRange>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CallLocations {
|
||||||
|
pub fn add(&mut self, target: &NavigationTarget, range: TextRange) {
|
||||||
|
self.funcs.entry(target.clone()).or_default().push(range);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_items(self) -> Vec<CallItem> {
|
||||||
|
self.funcs.into_iter().map(|(target, ranges)| CallItem { target, ranges }).collect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CallItem {
|
||||||
|
pub target: NavigationTarget,
|
||||||
|
pub ranges: Vec<TextRange>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CallItem {
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn assert_match(&self, expected: &str) {
|
||||||
|
let actual = self.debug_render();
|
||||||
|
test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn debug_render(&self) -> String {
|
||||||
|
format!("{} : {:?}", self.target.debug_render(), self.ranges)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn call_hierarchy(
|
||||||
|
db: &RootDatabase,
|
||||||
|
position: FilePosition,
|
||||||
|
) -> Option<RangeInfo<Vec<NavigationTarget>>> {
|
||||||
|
goto_definition::goto_definition(db, position)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> {
|
||||||
|
// 1. Find all refs
|
||||||
|
// 2. Loop through refs and determine unique fndef. This will become our `from: CallHierarchyItem,` in the reply.
|
||||||
|
// 3. Add ranges relative to the start of the fndef.
|
||||||
|
let refs = references::find_all_refs(db, position, None)?;
|
||||||
|
|
||||||
|
let mut calls = CallLocations::default();
|
||||||
|
|
||||||
|
for reference in refs.info.references() {
|
||||||
|
let file_id = reference.file_range.file_id;
|
||||||
|
let file = db.parse_or_expand(file_id.into())?;
|
||||||
|
let token = file.token_at_offset(reference.file_range.range.start()).next()?;
|
||||||
|
let token = descend_into_macros(db, file_id, token);
|
||||||
|
let syntax = token.value.parent();
|
||||||
|
|
||||||
|
// This target is the containing function
|
||||||
|
if let Some(nav) = syntax.ancestors().find_map(|node| {
|
||||||
|
match_ast! {
|
||||||
|
match node {
|
||||||
|
ast::FnDef(it) => {
|
||||||
|
Some(NavigationTarget::from_named(
|
||||||
|
db,
|
||||||
|
token.with_value(&it),
|
||||||
|
it.doc_comment_text(),
|
||||||
|
it.short_label(),
|
||||||
|
))
|
||||||
|
},
|
||||||
|
_ => { None },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
let relative_range = reference.file_range.range;
|
||||||
|
calls.add(&nav, relative_range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(calls.into_items())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> {
|
||||||
|
let file_id = position.file_id;
|
||||||
|
let file = db.parse_or_expand(file_id.into())?;
|
||||||
|
let token = file.token_at_offset(position.offset).next()?;
|
||||||
|
let token = descend_into_macros(db, file_id, token);
|
||||||
|
let syntax = token.value.parent();
|
||||||
|
|
||||||
|
let mut calls = CallLocations::default();
|
||||||
|
|
||||||
|
syntax
|
||||||
|
.descendants()
|
||||||
|
.filter_map(|node| FnCallNode::with_node_exact(&node))
|
||||||
|
.filter_map(|call_node| {
|
||||||
|
let name_ref = call_node.name_ref()?;
|
||||||
|
let name_ref = token.with_value(name_ref.syntax());
|
||||||
|
|
||||||
|
let analyzer = hir::SourceAnalyzer::new(db, name_ref, None);
|
||||||
|
|
||||||
|
if let Some(func_target) = match &call_node {
|
||||||
|
FnCallNode::CallExpr(expr) => {
|
||||||
|
//FIXME: Type::as_callable is broken
|
||||||
|
let callable_def = analyzer.type_of(db, &expr.expr()?)?.as_callable()?;
|
||||||
|
match callable_def {
|
||||||
|
hir::CallableDef::FunctionId(it) => {
|
||||||
|
let fn_def: hir::Function = it.into();
|
||||||
|
let nav = fn_def.to_nav(db);
|
||||||
|
Some(nav)
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FnCallNode::MethodCallExpr(expr) => {
|
||||||
|
let function = analyzer.resolve_method_call(&expr)?;
|
||||||
|
Some(function.to_nav(db))
|
||||||
|
}
|
||||||
|
FnCallNode::MacroCallExpr(expr) => {
|
||||||
|
let macro_def = analyzer.resolve_macro_call(db, name_ref.with_value(&expr))?;
|
||||||
|
Some(macro_def.to_nav(db))
|
||||||
|
}
|
||||||
|
} {
|
||||||
|
Some((func_target.clone(), name_ref.value.text_range()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.for_each(|(nav, range)| calls.add(&nav, range));
|
||||||
|
|
||||||
|
Some(calls.into_items())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use ra_db::FilePosition;
|
||||||
|
|
||||||
|
use crate::mock_analysis::analysis_and_position;
|
||||||
|
|
||||||
|
fn check_hierarchy(
|
||||||
|
fixture: &str,
|
||||||
|
expected: &str,
|
||||||
|
expected_incoming: &[&str],
|
||||||
|
expected_outgoing: &[&str],
|
||||||
|
) {
|
||||||
|
let (analysis, pos) = analysis_and_position(fixture);
|
||||||
|
|
||||||
|
let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info;
|
||||||
|
assert_eq!(navs.len(), 1);
|
||||||
|
let nav = navs.pop().unwrap();
|
||||||
|
nav.assert_match(expected);
|
||||||
|
|
||||||
|
let item_pos = FilePosition { file_id: nav.file_id(), offset: nav.range().start() };
|
||||||
|
let incoming_calls = analysis.incoming_calls(item_pos).unwrap().unwrap();
|
||||||
|
assert_eq!(incoming_calls.len(), expected_incoming.len());
|
||||||
|
|
||||||
|
for call in 0..incoming_calls.len() {
|
||||||
|
incoming_calls[call].assert_match(expected_incoming[call]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let outgoing_calls = analysis.outgoing_calls(item_pos).unwrap().unwrap();
|
||||||
|
assert_eq!(outgoing_calls.len(), expected_outgoing.len());
|
||||||
|
|
||||||
|
for call in 0..outgoing_calls.len() {
|
||||||
|
outgoing_calls[call].assert_match(expected_outgoing[call]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_call_hierarchy_on_ref() {
|
||||||
|
check_hierarchy(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs
|
||||||
|
fn callee() {}
|
||||||
|
fn caller() {
|
||||||
|
call<|>ee();
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
"callee FN_DEF FileId(1) [0; 14) [3; 9)",
|
||||||
|
&["caller FN_DEF FileId(1) [15; 44) [18; 24) : [[33; 39)]"],
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_call_hierarchy_on_def() {
|
||||||
|
check_hierarchy(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs
|
||||||
|
fn call<|>ee() {}
|
||||||
|
fn caller() {
|
||||||
|
callee();
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
"callee FN_DEF FileId(1) [0; 14) [3; 9)",
|
||||||
|
&["caller FN_DEF FileId(1) [15; 44) [18; 24) : [[33; 39)]"],
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_call_hierarchy_in_same_fn() {
|
||||||
|
check_hierarchy(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs
|
||||||
|
fn callee() {}
|
||||||
|
fn caller() {
|
||||||
|
call<|>ee();
|
||||||
|
callee();
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
"callee FN_DEF FileId(1) [0; 14) [3; 9)",
|
||||||
|
&["caller FN_DEF FileId(1) [15; 58) [18; 24) : [[33; 39), [47; 53)]"],
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_call_hierarchy_in_different_fn() {
|
||||||
|
check_hierarchy(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs
|
||||||
|
fn callee() {}
|
||||||
|
fn caller1() {
|
||||||
|
call<|>ee();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn caller2() {
|
||||||
|
callee();
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
"callee FN_DEF FileId(1) [0; 14) [3; 9)",
|
||||||
|
&[
|
||||||
|
"caller1 FN_DEF FileId(1) [15; 45) [18; 25) : [[34; 40)]",
|
||||||
|
"caller2 FN_DEF FileId(1) [46; 76) [49; 56) : [[65; 71)]",
|
||||||
|
],
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_call_hierarchy_in_different_files() {
|
||||||
|
check_hierarchy(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs
|
||||||
|
mod foo;
|
||||||
|
use foo::callee;
|
||||||
|
|
||||||
|
fn caller() {
|
||||||
|
call<|>ee();
|
||||||
|
}
|
||||||
|
|
||||||
|
//- /foo/mod.rs
|
||||||
|
pub fn callee() {}
|
||||||
|
"#,
|
||||||
|
"callee FN_DEF FileId(2) [0; 18) [7; 13)",
|
||||||
|
&["caller FN_DEF FileId(1) [26; 55) [29; 35) : [[44; 50)]"],
|
||||||
|
&[],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_call_hierarchy_outgoing() {
|
||||||
|
check_hierarchy(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs
|
||||||
|
fn callee() {}
|
||||||
|
fn call<|>er() {
|
||||||
|
callee();
|
||||||
|
callee();
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
"caller FN_DEF FileId(1) [15; 58) [18; 24)",
|
||||||
|
&[],
|
||||||
|
&["callee FN_DEF FileId(1) [0; 14) [3; 9) : [[33; 39), [47; 53)]"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_call_hierarchy_outgoing_in_different_files() {
|
||||||
|
check_hierarchy(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs
|
||||||
|
mod foo;
|
||||||
|
use foo::callee;
|
||||||
|
|
||||||
|
fn call<|>er() {
|
||||||
|
callee();
|
||||||
|
}
|
||||||
|
|
||||||
|
//- /foo/mod.rs
|
||||||
|
pub fn callee() {}
|
||||||
|
"#,
|
||||||
|
"caller FN_DEF FileId(1) [26; 55) [29; 35)",
|
||||||
|
&[],
|
||||||
|
&["callee FN_DEF FileId(2) [0; 18) [7; 13) : [[44; 50)]"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_call_hierarchy_incoming_outgoing() {
|
||||||
|
check_hierarchy(
|
||||||
|
r#"
|
||||||
|
//- /lib.rs
|
||||||
|
fn caller1() {
|
||||||
|
call<|>er2();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn caller2() {
|
||||||
|
caller3();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn caller3() {
|
||||||
|
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
"caller2 FN_DEF FileId(1) [32; 63) [35; 42)",
|
||||||
|
&["caller1 FN_DEF FileId(1) [0; 31) [3; 10) : [[19; 26)]"],
|
||||||
|
&["caller3 FN_DEF FileId(1) [64; 80) [67; 74) : [[51; 58)]"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -88,7 +88,7 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum FnCallNode {
|
pub(crate) enum FnCallNode {
|
||||||
CallExpr(ast::CallExpr),
|
CallExpr(ast::CallExpr),
|
||||||
MethodCallExpr(ast::MethodCallExpr),
|
MethodCallExpr(ast::MethodCallExpr),
|
||||||
MacroCallExpr(ast::MacroCall),
|
MacroCallExpr(ast::MacroCall),
|
||||||
|
@ -108,7 +108,18 @@ impl FnCallNode {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn name_ref(&self) -> Option<ast::NameRef> {
|
pub(crate) fn with_node_exact(node: &SyntaxNode) -> Option<FnCallNode> {
|
||||||
|
match_ast! {
|
||||||
|
match node {
|
||||||
|
ast::CallExpr(it) => { Some(FnCallNode::CallExpr(it)) },
|
||||||
|
ast::MethodCallExpr(it) => { Some(FnCallNode::MethodCallExpr(it)) },
|
||||||
|
ast::MacroCall(it) => { Some(FnCallNode::MacroCallExpr(it)) },
|
||||||
|
_ => { None },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn name_ref(&self) -> Option<ast::NameRef> {
|
||||||
match self {
|
match self {
|
||||||
FnCallNode::CallExpr(call_expr) => Some(match call_expr.expr()? {
|
FnCallNode::CallExpr(call_expr) => Some(match call_expr.expr()? {
|
||||||
ast::Expr::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?,
|
ast::Expr::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?,
|
||||||
|
|
|
@ -19,7 +19,7 @@ use super::short_label::ShortLabel;
|
||||||
///
|
///
|
||||||
/// Typically, a `NavigationTarget` corresponds to some element in the source
|
/// Typically, a `NavigationTarget` corresponds to some element in the source
|
||||||
/// code, like a function or a struct, but this is not strictly required.
|
/// code, like a function or a struct, but this is not strictly required.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub struct NavigationTarget {
|
pub struct NavigationTarget {
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
name: SmolStr,
|
name: SmolStr,
|
||||||
|
|
|
@ -24,6 +24,7 @@ mod goto_definition;
|
||||||
mod goto_type_definition;
|
mod goto_type_definition;
|
||||||
mod extend_selection;
|
mod extend_selection;
|
||||||
mod hover;
|
mod hover;
|
||||||
|
mod call_hierarchy;
|
||||||
mod call_info;
|
mod call_info;
|
||||||
mod syntax_highlighting;
|
mod syntax_highlighting;
|
||||||
mod parent_module;
|
mod parent_module;
|
||||||
|
@ -62,6 +63,7 @@ use crate::{db::LineIndexDatabase, display::ToNav, symbol_index::FileSymbol};
|
||||||
|
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
assists::{Assist, AssistId},
|
assists::{Assist, AssistId},
|
||||||
|
call_hierarchy::CallItem,
|
||||||
change::{AnalysisChange, LibraryData},
|
change::{AnalysisChange, LibraryData},
|
||||||
completion::{CompletionItem, CompletionItemKind, InsertTextFormat},
|
completion::{CompletionItem, CompletionItemKind, InsertTextFormat},
|
||||||
diagnostics::Severity,
|
diagnostics::Severity,
|
||||||
|
@ -412,6 +414,24 @@ impl Analysis {
|
||||||
self.with_db(|db| call_info::call_info(db, position))
|
self.with_db(|db| call_info::call_info(db, position))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Computes call hierarchy candidates for the given file position.
|
||||||
|
pub fn call_hierarchy(
|
||||||
|
&self,
|
||||||
|
position: FilePosition,
|
||||||
|
) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
|
||||||
|
self.with_db(|db| call_hierarchy::call_hierarchy(db, position))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes incoming calls for the given file position.
|
||||||
|
pub fn incoming_calls(&self, position: FilePosition) -> Cancelable<Option<Vec<CallItem>>> {
|
||||||
|
self.with_db(|db| call_hierarchy::incoming_calls(db, position))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes incoming calls for the given file position.
|
||||||
|
pub fn outgoing_calls(&self, position: FilePosition) -> Cancelable<Option<Vec<CallItem>>> {
|
||||||
|
self.with_db(|db| call_hierarchy::outgoing_calls(db, position))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns a `mod name;` declaration which created the current module.
|
/// Returns a `mod name;` declaration which created the current module.
|
||||||
pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<NavigationTarget>> {
|
pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<NavigationTarget>> {
|
||||||
self.with_db(|db| parent_module::parent_module(db, position))
|
self.with_db(|db| parent_module::parent_module(db, position))
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
//! Advertizes the capabilities of the LSP Server.
|
//! Advertizes the capabilities of the LSP Server.
|
||||||
|
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
CodeActionProviderCapability, CodeLensOptions, CompletionOptions,
|
CallHierarchyServerCapability, CodeActionProviderCapability, CodeLensOptions,
|
||||||
DocumentOnTypeFormattingOptions, FoldingRangeProviderCapability,
|
CompletionOptions, DocumentOnTypeFormattingOptions, FoldingRangeProviderCapability,
|
||||||
ImplementationProviderCapability, RenameOptions, RenameProviderCapability, SaveOptions,
|
ImplementationProviderCapability, RenameOptions, RenameProviderCapability, SaveOptions,
|
||||||
SelectionRangeProviderCapability, ServerCapabilities, SignatureHelpOptions,
|
SelectionRangeProviderCapability, ServerCapabilities, SignatureHelpOptions,
|
||||||
TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
|
TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions,
|
||||||
|
@ -56,7 +56,7 @@ pub fn server_capabilities() -> ServerCapabilities {
|
||||||
color_provider: None,
|
color_provider: None,
|
||||||
execute_command_provider: None,
|
execute_command_provider: None,
|
||||||
workspace: None,
|
workspace: None,
|
||||||
call_hierarchy_provider: None,
|
call_hierarchy_provider: Some(CallHierarchyServerCapability::Simple(true)),
|
||||||
experimental: Default::default(),
|
experimental: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -490,6 +490,24 @@ impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<Vec<NavigationTarget>>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_call_hierarchy_item(
|
||||||
|
file_id: FileId,
|
||||||
|
range: TextRange,
|
||||||
|
world: &WorldSnapshot,
|
||||||
|
line_index: &LineIndex,
|
||||||
|
nav: NavigationTarget,
|
||||||
|
) -> Result<lsp_types::CallHierarchyItem> {
|
||||||
|
Ok(lsp_types::CallHierarchyItem {
|
||||||
|
name: nav.name().to_string(),
|
||||||
|
kind: nav.kind().conv(),
|
||||||
|
tags: None,
|
||||||
|
detail: nav.description().map(|it| it.to_string()),
|
||||||
|
uri: file_id.try_conv_with(&world)?,
|
||||||
|
range: nav.range().conv_with(&line_index),
|
||||||
|
selection_range: range.conv_with(&line_index),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_location(
|
pub fn to_location(
|
||||||
file_id: FileId,
|
file_id: FileId,
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
|
|
|
@ -499,6 +499,9 @@ fn on_request(
|
||||||
.on::<req::Formatting>(handlers::handle_formatting)?
|
.on::<req::Formatting>(handlers::handle_formatting)?
|
||||||
.on::<req::DocumentHighlightRequest>(handlers::handle_document_highlight)?
|
.on::<req::DocumentHighlightRequest>(handlers::handle_document_highlight)?
|
||||||
.on::<req::InlayHints>(handlers::handle_inlay_hints)?
|
.on::<req::InlayHints>(handlers::handle_inlay_hints)?
|
||||||
|
.on::<req::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare)?
|
||||||
|
.on::<req::CallHierarchyIncomingCalls>(handlers::handle_call_hierarchy_incoming)?
|
||||||
|
.on::<req::CallHierarchyOutgoingCalls>(handlers::handle_call_hierarchy_outgoing)?
|
||||||
.finish();
|
.finish();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,16 @@ use std::{fmt::Write as _, io::Write as _};
|
||||||
|
|
||||||
use lsp_server::ErrorCode;
|
use lsp_server::ErrorCode;
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
|
CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem,
|
||||||
|
CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams,
|
||||||
CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic,
|
CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic,
|
||||||
DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams,
|
DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams,
|
||||||
Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse,
|
Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse,
|
||||||
Range, RenameParams, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit,
|
Range, RenameParams, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit,
|
||||||
};
|
};
|
||||||
use ra_ide::{
|
use ra_ide::{
|
||||||
AssistId, FileId, FilePosition, FileRange, Query, Runnable, RunnableKind, SearchScope,
|
AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind,
|
||||||
|
SearchScope,
|
||||||
};
|
};
|
||||||
use ra_prof::profile;
|
use ra_prof::profile;
|
||||||
use ra_syntax::{AstNode, SyntaxKind, TextRange, TextUnit};
|
use ra_syntax::{AstNode, SyntaxKind, TextRange, TextUnit};
|
||||||
|
@ -21,7 +24,10 @@ use serde_json::to_value;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
cargo_target_spec::{runnable_args, CargoTargetSpec},
|
cargo_target_spec::{runnable_args, CargoTargetSpec},
|
||||||
conv::{to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith, TryConvWithToVec},
|
conv::{
|
||||||
|
to_call_hierarchy_item, to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith,
|
||||||
|
TryConvWithToVec,
|
||||||
|
},
|
||||||
req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind},
|
req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind},
|
||||||
world::WorldSnapshot,
|
world::WorldSnapshot,
|
||||||
LspError, Result,
|
LspError, Result,
|
||||||
|
@ -936,3 +942,91 @@ pub fn handle_inlay_hints(
|
||||||
})
|
})
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_call_hierarchy_prepare(
|
||||||
|
world: WorldSnapshot,
|
||||||
|
params: CallHierarchyPrepareParams,
|
||||||
|
) -> Result<Option<Vec<CallHierarchyItem>>> {
|
||||||
|
let _p = profile("handle_call_hierarchy_prepare");
|
||||||
|
let position = params.text_document_position_params.try_conv_with(&world)?;
|
||||||
|
let file_id = position.file_id;
|
||||||
|
|
||||||
|
let nav_info = match world.analysis().call_hierarchy(position)? {
|
||||||
|
None => return Ok(None),
|
||||||
|
Some(it) => it,
|
||||||
|
};
|
||||||
|
|
||||||
|
let line_index = world.analysis().file_line_index(file_id)?;
|
||||||
|
let RangeInfo { range, info: navs } = nav_info;
|
||||||
|
let res = navs
|
||||||
|
.into_iter()
|
||||||
|
.filter(|it| it.kind() == SyntaxKind::FN_DEF)
|
||||||
|
.filter_map(|it| to_call_hierarchy_item(file_id, range, &world, &line_index, it).ok())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(Some(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_call_hierarchy_incoming(
|
||||||
|
world: WorldSnapshot,
|
||||||
|
params: CallHierarchyIncomingCallsParams,
|
||||||
|
) -> Result<Option<Vec<CallHierarchyIncomingCall>>> {
|
||||||
|
let _p = profile("handle_call_hierarchy_incoming");
|
||||||
|
let item = params.item;
|
||||||
|
|
||||||
|
let doc = TextDocumentIdentifier::new(item.uri);
|
||||||
|
let frange: FileRange = (&doc, item.range).try_conv_with(&world)?;
|
||||||
|
let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
|
||||||
|
|
||||||
|
let call_items = match world.analysis().incoming_calls(fpos)? {
|
||||||
|
None => return Ok(None),
|
||||||
|
Some(it) => it,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut res = vec![];
|
||||||
|
|
||||||
|
for call_item in call_items.into_iter() {
|
||||||
|
let file_id = call_item.target.file_id();
|
||||||
|
let line_index = world.analysis().file_line_index(file_id)?;
|
||||||
|
let range = call_item.target.range();
|
||||||
|
let item = to_call_hierarchy_item(file_id, range, &world, &line_index, call_item.target)?;
|
||||||
|
res.push(CallHierarchyIncomingCall {
|
||||||
|
from: item,
|
||||||
|
from_ranges: call_item.ranges.iter().map(|it| it.conv_with(&line_index)).collect(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(res))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn handle_call_hierarchy_outgoing(
|
||||||
|
world: WorldSnapshot,
|
||||||
|
params: CallHierarchyOutgoingCallsParams,
|
||||||
|
) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> {
|
||||||
|
let _p = profile("handle_call_hierarchy_outgoing");
|
||||||
|
let item = params.item;
|
||||||
|
|
||||||
|
let doc = TextDocumentIdentifier::new(item.uri);
|
||||||
|
let frange: FileRange = (&doc, item.range).try_conv_with(&world)?;
|
||||||
|
let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
|
||||||
|
|
||||||
|
let call_items = match world.analysis().outgoing_calls(fpos)? {
|
||||||
|
None => return Ok(None),
|
||||||
|
Some(it) => it,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut res = vec![];
|
||||||
|
|
||||||
|
for call_item in call_items.into_iter() {
|
||||||
|
let file_id = call_item.target.file_id();
|
||||||
|
let line_index = world.analysis().file_line_index(file_id)?;
|
||||||
|
let range = call_item.target.range();
|
||||||
|
let item = to_call_hierarchy_item(file_id, range, &world, &line_index, call_item.target)?;
|
||||||
|
res.push(CallHierarchyOutgoingCall {
|
||||||
|
to: item,
|
||||||
|
from_ranges: call_item.ranges.iter().map(|it| it.conv_with(&line_index)).collect(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(res))
|
||||||
|
}
|
||||||
|
|
|
@ -6,13 +6,15 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub use lsp_types::{
|
pub use lsp_types::{
|
||||||
notification::*, request::*, ApplyWorkspaceEditParams, CodeActionParams, CodeLens,
|
notification::*, request::*, ApplyWorkspaceEditParams, CodeActionParams, CodeLens,
|
||||||
CodeLensParams, CompletionParams, CompletionResponse, DidChangeConfigurationParams,
|
CodeLensParams, CompletionParams, CompletionResponse, DiagnosticTag,
|
||||||
DidChangeWatchedFilesParams, DidChangeWatchedFilesRegistrationOptions,
|
DidChangeConfigurationParams, DidChangeWatchedFilesParams,
|
||||||
DocumentOnTypeFormattingParams, DocumentSymbolParams, DocumentSymbolResponse,
|
DidChangeWatchedFilesRegistrationOptions, DocumentOnTypeFormattingParams, DocumentSymbolParams,
|
||||||
FileSystemWatcher, Hover, InitializeResult, MessageType, ProgressParams, ProgressParamsValue,
|
DocumentSymbolResponse, FileSystemWatcher, Hover, InitializeResult, MessageType,
|
||||||
ProgressToken, PublishDiagnosticsParams, ReferenceParams, Registration, RegistrationParams,
|
PartialResultParams, ProgressParams, ProgressParamsValue, ProgressToken,
|
||||||
SelectionRange, SelectionRangeParams, ShowMessageParams, SignatureHelp, TextDocumentEdit,
|
PublishDiagnosticsParams, ReferenceParams, Registration, RegistrationParams, SelectionRange,
|
||||||
TextDocumentPositionParams, TextEdit, WorkspaceEdit, WorkspaceSymbolParams,
|
SelectionRangeParams, ServerCapabilities, ShowMessageParams, SignatureHelp, SymbolKind,
|
||||||
|
TextDocumentEdit, TextDocumentPositionParams, TextEdit, WorkDoneProgressParams, WorkspaceEdit,
|
||||||
|
WorkspaceSymbolParams,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub enum AnalyzerStatus {}
|
pub enum AnalyzerStatus {}
|
||||||
|
|
Loading…
Reference in a new issue