diff options
author | Jeremy Kolb <[email protected]> | 2019-12-30 14:12:06 +0000 |
---|---|---|
committer | kjeremy <[email protected]> | 2020-01-08 15:15:49 +0000 |
commit | 1b19a8aa5ecfc9d7115f291b97d413bd845c89b5 (patch) | |
tree | cf209285ee7020bb0ab75b9a71eab4c006b86532 | |
parent | 928ecd069a508845ef4dbfd1bc1b9bf975d76e5b (diff) |
Implement proposed CallHierarchy feature
See: https://github.com/microsoft/vscode-languageserver-node/blob/master/protocol/src/protocol.callHierarchy.proposed.ts
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | crates/ra_ide/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/ra_ide/src/call_hierarchy.rs | 337 | ||||
-rw-r--r-- | crates/ra_ide/src/call_info.rs | 15 | ||||
-rw-r--r-- | crates/ra_ide/src/display/navigation_target.rs | 2 | ||||
-rw-r--r-- | crates/ra_ide/src/lib.rs | 20 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/caps.rs | 6 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/conv.rs | 18 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop.rs | 3 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 98 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/req.rs | 16 |
11 files changed, 502 insertions, 15 deletions
diff --git a/Cargo.lock b/Cargo.lock index 159b65d01..c570e0c3c 100644 --- a/Cargo.lock +++ b/Cargo.lock | |||
@@ -1032,6 +1032,7 @@ dependencies = [ | |||
1032 | "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", | 1032 | "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", |
1033 | "format-buf 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", | 1033 | "format-buf 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1034 | "fst 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", | 1034 | "fst 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", |
1035 | "indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", | ||
1035 | "insta 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", | 1036 | "insta 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", |
1036 | "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", | 1037 | "itertools 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", |
1037 | "join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", | 1038 | "join_to_string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", |
diff --git a/crates/ra_ide/Cargo.toml b/crates/ra_ide/Cargo.toml index e3439ae31..2c9f9dce0 100644 --- a/crates/ra_ide/Cargo.toml +++ b/crates/ra_ide/Cargo.toml | |||
@@ -13,6 +13,7 @@ wasm = [] | |||
13 | [dependencies] | 13 | [dependencies] |
14 | either = "1.5" | 14 | either = "1.5" |
15 | format-buf = "1.0.0" | 15 | format-buf = "1.0.0" |
16 | indexmap = "1.3.0" | ||
16 | itertools = "0.8.0" | 17 | itertools = "0.8.0" |
17 | join_to_string = "0.1.3" | 18 | join_to_string = "0.1.3" |
18 | log = "0.4.5" | 19 | log = "0.4.5" |
diff --git a/crates/ra_ide/src/call_hierarchy.rs b/crates/ra_ide/src/call_hierarchy.rs new file mode 100644 index 000000000..75658c20b --- /dev/null +++ b/crates/ra_ide/src/call_hierarchy.rs | |||
@@ -0,0 +1,337 @@ | |||
1 | //! Entry point for call-hierarchy | ||
2 | |||
3 | use indexmap::IndexMap; | ||
4 | |||
5 | use hir::db::AstDatabase; | ||
6 | use ra_syntax::{ | ||
7 | ast::{self, DocCommentsOwner}, | ||
8 | match_ast, AstNode, TextRange, | ||
9 | }; | ||
10 | |||
11 | use crate::{ | ||
12 | call_info::FnCallNode, | ||
13 | db::RootDatabase, | ||
14 | display::{ShortLabel, ToNav}, | ||
15 | expand::descend_into_macros, | ||
16 | goto_definition, references, FilePosition, NavigationTarget, RangeInfo, | ||
17 | }; | ||
18 | |||
19 | #[derive(Default)] | ||
20 | struct CallLocations { | ||
21 | funcs: IndexMap<NavigationTarget, Vec<TextRange>>, | ||
22 | } | ||
23 | |||
24 | impl CallLocations { | ||
25 | pub fn add(&mut self, target: &NavigationTarget, range: TextRange) { | ||
26 | self.funcs.entry(target.clone()).or_default().push(range); | ||
27 | } | ||
28 | |||
29 | pub fn into_items(self) -> Vec<CallItem> { | ||
30 | self.funcs.into_iter().map(|(target, ranges)| CallItem { target, ranges }).collect() | ||
31 | } | ||
32 | } | ||
33 | |||
34 | #[derive(Debug, Clone)] | ||
35 | pub struct CallItem { | ||
36 | pub target: NavigationTarget, | ||
37 | pub ranges: Vec<TextRange>, | ||
38 | } | ||
39 | |||
40 | impl CallItem { | ||
41 | #[cfg(test)] | ||
42 | pub(crate) fn assert_match(&self, expected: &str) { | ||
43 | let actual = self.debug_render(); | ||
44 | test_utils::assert_eq_text!(expected.trim(), actual.trim(),); | ||
45 | } | ||
46 | |||
47 | #[cfg(test)] | ||
48 | pub(crate) fn debug_render(&self) -> String { | ||
49 | format!("{} : {:?}", self.target.debug_render(), self.ranges) | ||
50 | } | ||
51 | } | ||
52 | |||
53 | pub(crate) fn call_hierarchy( | ||
54 | db: &RootDatabase, | ||
55 | position: FilePosition, | ||
56 | ) -> Option<RangeInfo<Vec<NavigationTarget>>> { | ||
57 | goto_definition::goto_definition(db, position) | ||
58 | } | ||
59 | |||
60 | pub(crate) fn incoming_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> { | ||
61 | // 1. Find all refs | ||
62 | // 2. Loop through refs and determine unique fndef. This will become our `from: CallHierarchyItem,` in the reply. | ||
63 | // 3. Add ranges relative to the start of the fndef. | ||
64 | let refs = references::find_all_refs(db, position, None)?; | ||
65 | |||
66 | let mut calls = CallLocations::default(); | ||
67 | |||
68 | for reference in refs.info.references() { | ||
69 | let file_id = reference.file_range.file_id; | ||
70 | let file = db.parse_or_expand(file_id.into())?; | ||
71 | let token = file.token_at_offset(reference.file_range.range.start()).next()?; | ||
72 | let token = descend_into_macros(db, file_id, token); | ||
73 | let syntax = token.value.parent(); | ||
74 | |||
75 | // This target is the containing function | ||
76 | if let Some(nav) = syntax.ancestors().find_map(|node| { | ||
77 | match_ast! { | ||
78 | match node { | ||
79 | ast::FnDef(it) => { | ||
80 | Some(NavigationTarget::from_named( | ||
81 | db, | ||
82 | token.with_value(&it), | ||
83 | it.doc_comment_text(), | ||
84 | it.short_label(), | ||
85 | )) | ||
86 | }, | ||
87 | _ => { None }, | ||
88 | } | ||
89 | } | ||
90 | }) { | ||
91 | let relative_range = reference.file_range.range; | ||
92 | calls.add(&nav, relative_range); | ||
93 | } | ||
94 | } | ||
95 | |||
96 | Some(calls.into_items()) | ||
97 | } | ||
98 | |||
99 | pub(crate) fn outgoing_calls(db: &RootDatabase, position: FilePosition) -> Option<Vec<CallItem>> { | ||
100 | let file_id = position.file_id; | ||
101 | let file = db.parse_or_expand(file_id.into())?; | ||
102 | let token = file.token_at_offset(position.offset).next()?; | ||
103 | let token = descend_into_macros(db, file_id, token); | ||
104 | let syntax = token.value.parent(); | ||
105 | |||
106 | let mut calls = CallLocations::default(); | ||
107 | |||
108 | syntax | ||
109 | .descendants() | ||
110 | .filter_map(|node| FnCallNode::with_node_exact(&node)) | ||
111 | .filter_map(|call_node| { | ||
112 | let name_ref = call_node.name_ref()?; | ||
113 | let name_ref = token.with_value(name_ref.syntax()); | ||
114 | |||
115 | let analyzer = hir::SourceAnalyzer::new(db, name_ref, None); | ||
116 | |||
117 | if let Some(func_target) = match &call_node { | ||
118 | FnCallNode::CallExpr(expr) => { | ||
119 | //FIXME: Type::as_callable is broken | ||
120 | let callable_def = analyzer.type_of(db, &expr.expr()?)?.as_callable()?; | ||
121 | match callable_def { | ||
122 | hir::CallableDef::FunctionId(it) => { | ||
123 | let fn_def: hir::Function = it.into(); | ||
124 | let nav = fn_def.to_nav(db); | ||
125 | Some(nav) | ||
126 | } | ||
127 | _ => None, | ||
128 | } | ||
129 | } | ||
130 | FnCallNode::MethodCallExpr(expr) => { | ||
131 | let function = analyzer.resolve_method_call(&expr)?; | ||
132 | Some(function.to_nav(db)) | ||
133 | } | ||
134 | FnCallNode::MacroCallExpr(expr) => { | ||
135 | let macro_def = analyzer.resolve_macro_call(db, name_ref.with_value(&expr))?; | ||
136 | Some(macro_def.to_nav(db)) | ||
137 | } | ||
138 | } { | ||
139 | Some((func_target.clone(), name_ref.value.text_range())) | ||
140 | } else { | ||
141 | None | ||
142 | } | ||
143 | }) | ||
144 | .for_each(|(nav, range)| calls.add(&nav, range)); | ||
145 | |||
146 | Some(calls.into_items()) | ||
147 | } | ||
148 | |||
149 | #[cfg(test)] | ||
150 | mod tests { | ||
151 | use ra_db::FilePosition; | ||
152 | |||
153 | use crate::mock_analysis::analysis_and_position; | ||
154 | |||
155 | fn check_hierarchy( | ||
156 | fixture: &str, | ||
157 | expected: &str, | ||
158 | expected_incoming: &[&str], | ||
159 | expected_outgoing: &[&str], | ||
160 | ) { | ||
161 | let (analysis, pos) = analysis_and_position(fixture); | ||
162 | |||
163 | let mut navs = analysis.call_hierarchy(pos).unwrap().unwrap().info; | ||
164 | assert_eq!(navs.len(), 1); | ||
165 | let nav = navs.pop().unwrap(); | ||
166 | nav.assert_match(expected); | ||
167 | |||
168 | let item_pos = FilePosition { file_id: nav.file_id(), offset: nav.range().start() }; | ||
169 | let incoming_calls = analysis.incoming_calls(item_pos).unwrap().unwrap(); | ||
170 | assert_eq!(incoming_calls.len(), expected_incoming.len()); | ||
171 | |||
172 | for call in 0..incoming_calls.len() { | ||
173 | incoming_calls[call].assert_match(expected_incoming[call]); | ||
174 | } | ||
175 | |||
176 | let outgoing_calls = analysis.outgoing_calls(item_pos).unwrap().unwrap(); | ||
177 | assert_eq!(outgoing_calls.len(), expected_outgoing.len()); | ||
178 | |||
179 | for call in 0..outgoing_calls.len() { | ||
180 | outgoing_calls[call].assert_match(expected_outgoing[call]); | ||
181 | } | ||
182 | } | ||
183 | |||
184 | #[test] | ||
185 | fn test_call_hierarchy_on_ref() { | ||
186 | check_hierarchy( | ||
187 | r#" | ||
188 | //- /lib.rs | ||
189 | fn callee() {} | ||
190 | fn caller() { | ||
191 | call<|>ee(); | ||
192 | } | ||
193 | "#, | ||
194 | "callee FN_DEF FileId(1) [0; 14) [3; 9)", | ||
195 | &["caller FN_DEF FileId(1) [15; 44) [18; 24) : [[33; 39)]"], | ||
196 | &[], | ||
197 | ); | ||
198 | } | ||
199 | |||
200 | #[test] | ||
201 | fn test_call_hierarchy_on_def() { | ||
202 | check_hierarchy( | ||
203 | r#" | ||
204 | //- /lib.rs | ||
205 | fn call<|>ee() {} | ||
206 | fn caller() { | ||
207 | callee(); | ||
208 | } | ||
209 | "#, | ||
210 | "callee FN_DEF FileId(1) [0; 14) [3; 9)", | ||
211 | &["caller FN_DEF FileId(1) [15; 44) [18; 24) : [[33; 39)]"], | ||
212 | &[], | ||
213 | ); | ||
214 | } | ||
215 | |||
216 | #[test] | ||
217 | fn test_call_hierarchy_in_same_fn() { | ||
218 | check_hierarchy( | ||
219 | r#" | ||
220 | //- /lib.rs | ||
221 | fn callee() {} | ||
222 | fn caller() { | ||
223 | call<|>ee(); | ||
224 | callee(); | ||
225 | } | ||
226 | "#, | ||
227 | "callee FN_DEF FileId(1) [0; 14) [3; 9)", | ||
228 | &["caller FN_DEF FileId(1) [15; 58) [18; 24) : [[33; 39), [47; 53)]"], | ||
229 | &[], | ||
230 | ); | ||
231 | } | ||
232 | |||
233 | #[test] | ||
234 | fn test_call_hierarchy_in_different_fn() { | ||
235 | check_hierarchy( | ||
236 | r#" | ||
237 | //- /lib.rs | ||
238 | fn callee() {} | ||
239 | fn caller1() { | ||
240 | call<|>ee(); | ||
241 | } | ||
242 | |||
243 | fn caller2() { | ||
244 | callee(); | ||
245 | } | ||
246 | "#, | ||
247 | "callee FN_DEF FileId(1) [0; 14) [3; 9)", | ||
248 | &[ | ||
249 | "caller1 FN_DEF FileId(1) [15; 45) [18; 25) : [[34; 40)]", | ||
250 | "caller2 FN_DEF FileId(1) [46; 76) [49; 56) : [[65; 71)]", | ||
251 | ], | ||
252 | &[], | ||
253 | ); | ||
254 | } | ||
255 | |||
256 | #[test] | ||
257 | fn test_call_hierarchy_in_different_files() { | ||
258 | check_hierarchy( | ||
259 | r#" | ||
260 | //- /lib.rs | ||
261 | mod foo; | ||
262 | use foo::callee; | ||
263 | |||
264 | fn caller() { | ||
265 | call<|>ee(); | ||
266 | } | ||
267 | |||
268 | //- /foo/mod.rs | ||
269 | pub fn callee() {} | ||
270 | "#, | ||
271 | "callee FN_DEF FileId(2) [0; 18) [7; 13)", | ||
272 | &["caller FN_DEF FileId(1) [26; 55) [29; 35) : [[44; 50)]"], | ||
273 | &[], | ||
274 | ); | ||
275 | } | ||
276 | |||
277 | #[test] | ||
278 | fn test_call_hierarchy_outgoing() { | ||
279 | check_hierarchy( | ||
280 | r#" | ||
281 | //- /lib.rs | ||
282 | fn callee() {} | ||
283 | fn call<|>er() { | ||
284 | callee(); | ||
285 | callee(); | ||
286 | } | ||
287 | "#, | ||
288 | "caller FN_DEF FileId(1) [15; 58) [18; 24)", | ||
289 | &[], | ||
290 | &["callee FN_DEF FileId(1) [0; 14) [3; 9) : [[33; 39), [47; 53)]"], | ||
291 | ); | ||
292 | } | ||
293 | |||
294 | #[test] | ||
295 | fn test_call_hierarchy_outgoing_in_different_files() { | ||
296 | check_hierarchy( | ||
297 | r#" | ||
298 | //- /lib.rs | ||
299 | mod foo; | ||
300 | use foo::callee; | ||
301 | |||
302 | fn call<|>er() { | ||
303 | callee(); | ||
304 | } | ||
305 | |||
306 | //- /foo/mod.rs | ||
307 | pub fn callee() {} | ||
308 | "#, | ||
309 | "caller FN_DEF FileId(1) [26; 55) [29; 35)", | ||
310 | &[], | ||
311 | &["callee FN_DEF FileId(2) [0; 18) [7; 13) : [[44; 50)]"], | ||
312 | ); | ||
313 | } | ||
314 | |||
315 | #[test] | ||
316 | fn test_call_hierarchy_incoming_outgoing() { | ||
317 | check_hierarchy( | ||
318 | r#" | ||
319 | //- /lib.rs | ||
320 | fn caller1() { | ||
321 | call<|>er2(); | ||
322 | } | ||
323 | |||
324 | fn caller2() { | ||
325 | caller3(); | ||
326 | } | ||
327 | |||
328 | fn caller3() { | ||
329 | |||
330 | } | ||
331 | "#, | ||
332 | "caller2 FN_DEF FileId(1) [32; 63) [35; 42)", | ||
333 | &["caller1 FN_DEF FileId(1) [0; 31) [3; 10) : [[19; 26)]"], | ||
334 | &["caller3 FN_DEF FileId(1) [64; 80) [67; 74) : [[51; 58)]"], | ||
335 | ); | ||
336 | } | ||
337 | } | ||
diff --git a/crates/ra_ide/src/call_info.rs b/crates/ra_ide/src/call_info.rs index 2c2b6fa48..a7023529b 100644 --- a/crates/ra_ide/src/call_info.rs +++ b/crates/ra_ide/src/call_info.rs | |||
@@ -88,7 +88,7 @@ pub(crate) fn call_info(db: &RootDatabase, position: FilePosition) -> Option<Cal | |||
88 | } | 88 | } |
89 | 89 | ||
90 | #[derive(Debug)] | 90 | #[derive(Debug)] |
91 | enum FnCallNode { | 91 | pub(crate) enum FnCallNode { |
92 | CallExpr(ast::CallExpr), | 92 | CallExpr(ast::CallExpr), |
93 | MethodCallExpr(ast::MethodCallExpr), | 93 | MethodCallExpr(ast::MethodCallExpr), |
94 | MacroCallExpr(ast::MacroCall), | 94 | MacroCallExpr(ast::MacroCall), |
@@ -108,7 +108,18 @@ impl FnCallNode { | |||
108 | }) | 108 | }) |
109 | } | 109 | } |
110 | 110 | ||
111 | fn name_ref(&self) -> Option<ast::NameRef> { | 111 | pub(crate) fn with_node_exact(node: &SyntaxNode) -> Option<FnCallNode> { |
112 | match_ast! { | ||
113 | match node { | ||
114 | ast::CallExpr(it) => { Some(FnCallNode::CallExpr(it)) }, | ||
115 | ast::MethodCallExpr(it) => { Some(FnCallNode::MethodCallExpr(it)) }, | ||
116 | ast::MacroCall(it) => { Some(FnCallNode::MacroCallExpr(it)) }, | ||
117 | _ => { None }, | ||
118 | } | ||
119 | } | ||
120 | } | ||
121 | |||
122 | pub(crate) fn name_ref(&self) -> Option<ast::NameRef> { | ||
112 | match self { | 123 | match self { |
113 | FnCallNode::CallExpr(call_expr) => Some(match call_expr.expr()? { | 124 | FnCallNode::CallExpr(call_expr) => Some(match call_expr.expr()? { |
114 | ast::Expr::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?, | 125 | ast::Expr::PathExpr(path_expr) => path_expr.path()?.segment()?.name_ref()?, |
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs index b9ae67828..f2e45fa31 100644 --- a/crates/ra_ide/src/display/navigation_target.rs +++ b/crates/ra_ide/src/display/navigation_target.rs | |||
@@ -19,7 +19,7 @@ use super::short_label::ShortLabel; | |||
19 | /// | 19 | /// |
20 | /// Typically, a `NavigationTarget` corresponds to some element in the source | 20 | /// Typically, a `NavigationTarget` corresponds to some element in the source |
21 | /// code, like a function or a struct, but this is not strictly required. | 21 | /// code, like a function or a struct, but this is not strictly required. |
22 | #[derive(Debug, Clone)] | 22 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
23 | pub struct NavigationTarget { | 23 | pub struct NavigationTarget { |
24 | file_id: FileId, | 24 | file_id: FileId, |
25 | name: SmolStr, | 25 | name: SmolStr, |
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 779a81b2c..06497617b 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -24,6 +24,7 @@ mod goto_definition; | |||
24 | mod goto_type_definition; | 24 | mod goto_type_definition; |
25 | mod extend_selection; | 25 | mod extend_selection; |
26 | mod hover; | 26 | mod hover; |
27 | mod call_hierarchy; | ||
27 | mod call_info; | 28 | mod call_info; |
28 | mod syntax_highlighting; | 29 | mod syntax_highlighting; |
29 | mod parent_module; | 30 | mod parent_module; |
@@ -62,6 +63,7 @@ use crate::{db::LineIndexDatabase, display::ToNav, symbol_index::FileSymbol}; | |||
62 | 63 | ||
63 | pub use crate::{ | 64 | pub use crate::{ |
64 | assists::{Assist, AssistId}, | 65 | assists::{Assist, AssistId}, |
66 | call_hierarchy::CallItem, | ||
65 | change::{AnalysisChange, LibraryData}, | 67 | change::{AnalysisChange, LibraryData}, |
66 | completion::{CompletionItem, CompletionItemKind, InsertTextFormat}, | 68 | completion::{CompletionItem, CompletionItemKind, InsertTextFormat}, |
67 | diagnostics::Severity, | 69 | diagnostics::Severity, |
@@ -412,6 +414,24 @@ impl Analysis { | |||
412 | self.with_db(|db| call_info::call_info(db, position)) | 414 | self.with_db(|db| call_info::call_info(db, position)) |
413 | } | 415 | } |
414 | 416 | ||
417 | /// Computes call hierarchy candidates for the given file position. | ||
418 | pub fn call_hierarchy( | ||
419 | &self, | ||
420 | position: FilePosition, | ||
421 | ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> { | ||
422 | self.with_db(|db| call_hierarchy::call_hierarchy(db, position)) | ||
423 | } | ||
424 | |||
425 | /// Computes incoming calls for the given file position. | ||
426 | pub fn incoming_calls(&self, position: FilePosition) -> Cancelable<Option<Vec<CallItem>>> { | ||
427 | self.with_db(|db| call_hierarchy::incoming_calls(db, position)) | ||
428 | } | ||
429 | |||
430 | /// Computes incoming calls for the given file position. | ||
431 | pub fn outgoing_calls(&self, position: FilePosition) -> Cancelable<Option<Vec<CallItem>>> { | ||
432 | self.with_db(|db| call_hierarchy::outgoing_calls(db, position)) | ||
433 | } | ||
434 | |||
415 | /// Returns a `mod name;` declaration which created the current module. | 435 | /// Returns a `mod name;` declaration which created the current module. |
416 | pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<NavigationTarget>> { | 436 | pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<NavigationTarget>> { |
417 | self.with_db(|db| parent_module::parent_module(db, position)) | 437 | self.with_db(|db| parent_module::parent_module(db, position)) |
diff --git a/crates/ra_lsp_server/src/caps.rs b/crates/ra_lsp_server/src/caps.rs index db502c200..c4711076c 100644 --- a/crates/ra_lsp_server/src/caps.rs +++ b/crates/ra_lsp_server/src/caps.rs | |||
@@ -1,8 +1,8 @@ | |||
1 | //! Advertizes the capabilities of the LSP Server. | 1 | //! Advertizes the capabilities of the LSP Server. |
2 | 2 | ||
3 | use lsp_types::{ | 3 | use lsp_types::{ |
4 | CodeActionProviderCapability, CodeLensOptions, CompletionOptions, | 4 | CallHierarchyServerCapability, CodeActionProviderCapability, CodeLensOptions, |
5 | DocumentOnTypeFormattingOptions, FoldingRangeProviderCapability, | 5 | CompletionOptions, DocumentOnTypeFormattingOptions, FoldingRangeProviderCapability, |
6 | ImplementationProviderCapability, RenameOptions, RenameProviderCapability, SaveOptions, | 6 | ImplementationProviderCapability, RenameOptions, RenameProviderCapability, SaveOptions, |
7 | SelectionRangeProviderCapability, ServerCapabilities, SignatureHelpOptions, | 7 | SelectionRangeProviderCapability, ServerCapabilities, SignatureHelpOptions, |
8 | TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, | 8 | TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, |
@@ -56,7 +56,7 @@ pub fn server_capabilities() -> ServerCapabilities { | |||
56 | color_provider: None, | 56 | color_provider: None, |
57 | execute_command_provider: None, | 57 | execute_command_provider: None, |
58 | workspace: None, | 58 | workspace: None, |
59 | call_hierarchy_provider: None, | 59 | call_hierarchy_provider: Some(CallHierarchyServerCapability::Simple(true)), |
60 | experimental: Default::default(), | 60 | experimental: Default::default(), |
61 | } | 61 | } |
62 | } | 62 | } |
diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs index e93d4ea33..c260b51c4 100644 --- a/crates/ra_lsp_server/src/conv.rs +++ b/crates/ra_lsp_server/src/conv.rs | |||
@@ -490,6 +490,24 @@ impl TryConvWith<&WorldSnapshot> for (FileId, RangeInfo<Vec<NavigationTarget>>) | |||
490 | } | 490 | } |
491 | } | 491 | } |
492 | 492 | ||
493 | pub fn to_call_hierarchy_item( | ||
494 | file_id: FileId, | ||
495 | range: TextRange, | ||
496 | world: &WorldSnapshot, | ||
497 | line_index: &LineIndex, | ||
498 | nav: NavigationTarget, | ||
499 | ) -> Result<lsp_types::CallHierarchyItem> { | ||
500 | Ok(lsp_types::CallHierarchyItem { | ||
501 | name: nav.name().to_string(), | ||
502 | kind: nav.kind().conv(), | ||
503 | tags: None, | ||
504 | detail: nav.description().map(|it| it.to_string()), | ||
505 | uri: file_id.try_conv_with(&world)?, | ||
506 | range: nav.range().conv_with(&line_index), | ||
507 | selection_range: range.conv_with(&line_index), | ||
508 | }) | ||
509 | } | ||
510 | |||
493 | pub fn to_location( | 511 | pub fn to_location( |
494 | file_id: FileId, | 512 | file_id: FileId, |
495 | range: TextRange, | 513 | range: TextRange, |
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index 4336583fe..047c86a3b 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs | |||
@@ -499,6 +499,9 @@ fn on_request( | |||
499 | .on::<req::Formatting>(handlers::handle_formatting)? | 499 | .on::<req::Formatting>(handlers::handle_formatting)? |
500 | .on::<req::DocumentHighlightRequest>(handlers::handle_document_highlight)? | 500 | .on::<req::DocumentHighlightRequest>(handlers::handle_document_highlight)? |
501 | .on::<req::InlayHints>(handlers::handle_inlay_hints)? | 501 | .on::<req::InlayHints>(handlers::handle_inlay_hints)? |
502 | .on::<req::CallHierarchyPrepare>(handlers::handle_call_hierarchy_prepare)? | ||
503 | .on::<req::CallHierarchyIncomingCalls>(handlers::handle_call_hierarchy_incoming)? | ||
504 | .on::<req::CallHierarchyOutgoingCalls>(handlers::handle_call_hierarchy_outgoing)? | ||
502 | .finish(); | 505 | .finish(); |
503 | Ok(()) | 506 | Ok(()) |
504 | } | 507 | } |
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index c8f52eb0e..a5b6f48af 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs | |||
@@ -5,13 +5,16 @@ use std::{fmt::Write as _, io::Write as _}; | |||
5 | 5 | ||
6 | use lsp_server::ErrorCode; | 6 | use lsp_server::ErrorCode; |
7 | use lsp_types::{ | 7 | use lsp_types::{ |
8 | CallHierarchyIncomingCall, CallHierarchyIncomingCallsParams, CallHierarchyItem, | ||
9 | CallHierarchyOutgoingCall, CallHierarchyOutgoingCallsParams, CallHierarchyPrepareParams, | ||
8 | CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic, | 10 | CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic, |
9 | DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams, | 11 | DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeParams, |
10 | Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse, | 12 | Hover, HoverContents, Location, MarkupContent, MarkupKind, Position, PrepareRenameResponse, |
11 | Range, RenameParams, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit, | 13 | Range, RenameParams, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit, |
12 | }; | 14 | }; |
13 | use ra_ide::{ | 15 | use ra_ide::{ |
14 | AssistId, FileId, FilePosition, FileRange, Query, Runnable, RunnableKind, SearchScope, | 16 | AssistId, FileId, FilePosition, FileRange, Query, RangeInfo, Runnable, RunnableKind, |
17 | SearchScope, | ||
15 | }; | 18 | }; |
16 | use ra_prof::profile; | 19 | use ra_prof::profile; |
17 | use ra_syntax::{AstNode, SyntaxKind, TextRange, TextUnit}; | 20 | use ra_syntax::{AstNode, SyntaxKind, TextRange, TextUnit}; |
@@ -21,7 +24,10 @@ use serde_json::to_value; | |||
21 | 24 | ||
22 | use crate::{ | 25 | use crate::{ |
23 | cargo_target_spec::{runnable_args, CargoTargetSpec}, | 26 | cargo_target_spec::{runnable_args, CargoTargetSpec}, |
24 | conv::{to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith, TryConvWithToVec}, | 27 | conv::{ |
28 | to_call_hierarchy_item, to_location, Conv, ConvWith, FoldConvCtx, MapConvWith, TryConvWith, | ||
29 | TryConvWithToVec, | ||
30 | }, | ||
25 | req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind}, | 31 | req::{self, Decoration, InlayHint, InlayHintsParams, InlayKind}, |
26 | world::WorldSnapshot, | 32 | world::WorldSnapshot, |
27 | LspError, Result, | 33 | LspError, Result, |
@@ -936,3 +942,91 @@ pub fn handle_inlay_hints( | |||
936 | }) | 942 | }) |
937 | .collect()) | 943 | .collect()) |
938 | } | 944 | } |
945 | |||
946 | pub fn handle_call_hierarchy_prepare( | ||
947 | world: WorldSnapshot, | ||
948 | params: CallHierarchyPrepareParams, | ||
949 | ) -> Result<Option<Vec<CallHierarchyItem>>> { | ||
950 | let _p = profile("handle_call_hierarchy_prepare"); | ||
951 | let position = params.text_document_position_params.try_conv_with(&world)?; | ||
952 | let file_id = position.file_id; | ||
953 | |||
954 | let nav_info = match world.analysis().call_hierarchy(position)? { | ||
955 | None => return Ok(None), | ||
956 | Some(it) => it, | ||
957 | }; | ||
958 | |||
959 | let line_index = world.analysis().file_line_index(file_id)?; | ||
960 | let RangeInfo { range, info: navs } = nav_info; | ||
961 | let res = navs | ||
962 | .into_iter() | ||
963 | .filter(|it| it.kind() == SyntaxKind::FN_DEF) | ||
964 | .filter_map(|it| to_call_hierarchy_item(file_id, range, &world, &line_index, it).ok()) | ||
965 | .collect(); | ||
966 | |||
967 | Ok(Some(res)) | ||
968 | } | ||
969 | |||
970 | pub fn handle_call_hierarchy_incoming( | ||
971 | world: WorldSnapshot, | ||
972 | params: CallHierarchyIncomingCallsParams, | ||
973 | ) -> Result<Option<Vec<CallHierarchyIncomingCall>>> { | ||
974 | let _p = profile("handle_call_hierarchy_incoming"); | ||
975 | let item = params.item; | ||
976 | |||
977 | let doc = TextDocumentIdentifier::new(item.uri); | ||
978 | let frange: FileRange = (&doc, item.range).try_conv_with(&world)?; | ||
979 | let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; | ||
980 | |||
981 | let call_items = match world.analysis().incoming_calls(fpos)? { | ||
982 | None => return Ok(None), | ||
983 | Some(it) => it, | ||
984 | }; | ||
985 | |||
986 | let mut res = vec![]; | ||
987 | |||
988 | for call_item in call_items.into_iter() { | ||
989 | let file_id = call_item.target.file_id(); | ||
990 | let line_index = world.analysis().file_line_index(file_id)?; | ||
991 | let range = call_item.target.range(); | ||
992 | let item = to_call_hierarchy_item(file_id, range, &world, &line_index, call_item.target)?; | ||
993 | res.push(CallHierarchyIncomingCall { | ||
994 | from: item, | ||
995 | from_ranges: call_item.ranges.iter().map(|it| it.conv_with(&line_index)).collect(), | ||
996 | }); | ||
997 | } | ||
998 | |||
999 | Ok(Some(res)) | ||
1000 | } | ||
1001 | |||
1002 | pub fn handle_call_hierarchy_outgoing( | ||
1003 | world: WorldSnapshot, | ||
1004 | params: CallHierarchyOutgoingCallsParams, | ||
1005 | ) -> Result<Option<Vec<CallHierarchyOutgoingCall>>> { | ||
1006 | let _p = profile("handle_call_hierarchy_outgoing"); | ||
1007 | let item = params.item; | ||
1008 | |||
1009 | let doc = TextDocumentIdentifier::new(item.uri); | ||
1010 | let frange: FileRange = (&doc, item.range).try_conv_with(&world)?; | ||
1011 | let fpos = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; | ||
1012 | |||
1013 | let call_items = match world.analysis().outgoing_calls(fpos)? { | ||
1014 | None => return Ok(None), | ||
1015 | Some(it) => it, | ||
1016 | }; | ||
1017 | |||
1018 | let mut res = vec![]; | ||
1019 | |||
1020 | for call_item in call_items.into_iter() { | ||
1021 | let file_id = call_item.target.file_id(); | ||
1022 | let line_index = world.analysis().file_line_index(file_id)?; | ||
1023 | let range = call_item.target.range(); | ||
1024 | let item = to_call_hierarchy_item(file_id, range, &world, &line_index, call_item.target)?; | ||
1025 | res.push(CallHierarchyOutgoingCall { | ||
1026 | to: item, | ||
1027 | from_ranges: call_item.ranges.iter().map(|it| it.conv_with(&line_index)).collect(), | ||
1028 | }); | ||
1029 | } | ||
1030 | |||
1031 | Ok(Some(res)) | ||
1032 | } | ||
diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs index 40edaf677..8098ff31d 100644 --- a/crates/ra_lsp_server/src/req.rs +++ b/crates/ra_lsp_server/src/req.rs | |||
@@ -6,13 +6,15 @@ use serde::{Deserialize, Serialize}; | |||
6 | 6 | ||
7 | pub use lsp_types::{ | 7 | pub use lsp_types::{ |
8 | notification::*, request::*, ApplyWorkspaceEditParams, CodeActionParams, CodeLens, | 8 | notification::*, request::*, ApplyWorkspaceEditParams, CodeActionParams, CodeLens, |
9 | CodeLensParams, CompletionParams, CompletionResponse, DidChangeConfigurationParams, | 9 | CodeLensParams, CompletionParams, CompletionResponse, DiagnosticTag, |
10 | DidChangeWatchedFilesParams, DidChangeWatchedFilesRegistrationOptions, | 10 | DidChangeConfigurationParams, DidChangeWatchedFilesParams, |
11 | DocumentOnTypeFormattingParams, DocumentSymbolParams, DocumentSymbolResponse, | 11 | DidChangeWatchedFilesRegistrationOptions, DocumentOnTypeFormattingParams, DocumentSymbolParams, |
12 | FileSystemWatcher, Hover, InitializeResult, MessageType, ProgressParams, ProgressParamsValue, | 12 | DocumentSymbolResponse, FileSystemWatcher, Hover, InitializeResult, MessageType, |
13 | ProgressToken, PublishDiagnosticsParams, ReferenceParams, Registration, RegistrationParams, | 13 | PartialResultParams, ProgressParams, ProgressParamsValue, ProgressToken, |
14 | SelectionRange, SelectionRangeParams, ShowMessageParams, SignatureHelp, TextDocumentEdit, | 14 | PublishDiagnosticsParams, ReferenceParams, Registration, RegistrationParams, SelectionRange, |
15 | TextDocumentPositionParams, TextEdit, WorkspaceEdit, WorkspaceSymbolParams, | 15 | SelectionRangeParams, ServerCapabilities, ShowMessageParams, SignatureHelp, SymbolKind, |
16 | TextDocumentEdit, TextDocumentPositionParams, TextEdit, WorkDoneProgressParams, WorkspaceEdit, | ||
17 | WorkspaceSymbolParams, | ||
16 | }; | 18 | }; |
17 | 19 | ||
18 | pub enum AnalyzerStatus {} | 20 | pub enum AnalyzerStatus {} |