diff options
Diffstat (limited to 'crates/ra_ide/src')
-rw-r--r-- | crates/ra_ide/src/display/navigation_target.rs | 19 | ||||
-rw-r--r-- | crates/ra_ide/src/expand.rs | 79 | ||||
-rw-r--r-- | crates/ra_ide/src/goto_definition.rs | 34 |
3 files changed, 85 insertions, 47 deletions
diff --git a/crates/ra_ide/src/display/navigation_target.rs b/crates/ra_ide/src/display/navigation_target.rs index 6a6b49afd..6a2bf7273 100644 --- a/crates/ra_ide/src/display/navigation_target.rs +++ b/crates/ra_ide/src/display/navigation_target.rs | |||
@@ -7,10 +7,14 @@ use ra_syntax::{ | |||
7 | ast::{self, DocCommentsOwner, NameOwner}, | 7 | ast::{self, DocCommentsOwner, NameOwner}, |
8 | match_ast, AstNode, SmolStr, | 8 | match_ast, AstNode, SmolStr, |
9 | SyntaxKind::{self, BIND_PAT, TYPE_PARAM}, | 9 | SyntaxKind::{self, BIND_PAT, TYPE_PARAM}, |
10 | TextRange, | 10 | SyntaxNode, TextRange, |
11 | }; | 11 | }; |
12 | 12 | ||
13 | use crate::{db::RootDatabase, expand::original_range, FileSymbol}; | 13 | use crate::{ |
14 | db::RootDatabase, | ||
15 | expand::{original_range_by_kind, OriginalRangeKind}, | ||
16 | FileRange, FileSymbol, | ||
17 | }; | ||
14 | 18 | ||
15 | use super::short_label::ShortLabel; | 19 | use super::short_label::ShortLabel; |
16 | 20 | ||
@@ -416,3 +420,14 @@ pub(crate) fn description_from_symbol(db: &RootDatabase, symbol: &FileSymbol) -> | |||
416 | } | 420 | } |
417 | } | 421 | } |
418 | } | 422 | } |
423 | |||
424 | fn original_range(db: &RootDatabase, node: InFile<&SyntaxNode>) -> FileRange { | ||
425 | if let Some(range) = original_range_by_kind(db, node, OriginalRangeKind::CallToken) { | ||
426 | return range; | ||
427 | } | ||
428 | if let Some(range) = original_range_by_kind(db, node, OriginalRangeKind::WholeCall) { | ||
429 | return range; | ||
430 | } | ||
431 | |||
432 | FileRange { file_id: node.file_id.original_file(db), range: node.value.text_range() } | ||
433 | } | ||
diff --git a/crates/ra_ide/src/expand.rs b/crates/ra_ide/src/expand.rs index 661628ae4..327393dbb 100644 --- a/crates/ra_ide/src/expand.rs +++ b/crates/ra_ide/src/expand.rs | |||
@@ -1,57 +1,62 @@ | |||
1 | //! Utilities to work with files, produced by macros. | 1 | //! Utilities to work with files, produced by macros. |
2 | use std::iter::successors; | 2 | use std::iter::successors; |
3 | 3 | ||
4 | use hir::InFile; | 4 | use hir::{ExpansionOrigin, InFile}; |
5 | use ra_db::FileId; | 5 | use ra_db::FileId; |
6 | use ra_syntax::{ast, AstNode, SyntaxNode, SyntaxToken, TextRange}; | 6 | use ra_syntax::{ast, AstNode, SyntaxNode, SyntaxToken, TextRange}; |
7 | 7 | ||
8 | use crate::{db::RootDatabase, FileRange}; | 8 | use crate::{db::RootDatabase, FileRange}; |
9 | 9 | ||
10 | pub(crate) fn original_range(db: &RootDatabase, node: InFile<&SyntaxNode>) -> FileRange { | 10 | #[derive(Debug, PartialEq, Eq)] |
11 | let expansion = match node.file_id.expansion_info(db) { | 11 | pub(crate) enum OriginalRangeKind { |
12 | None => { | 12 | /// Return range if any token is matched |
13 | return FileRange { | 13 | #[allow(dead_code)] |
14 | file_id: node.file_id.original_file(db), | 14 | Any, |
15 | range: node.value.text_range(), | 15 | /// Return range if token is inside macro_call |
16 | } | 16 | CallToken, |
17 | } | 17 | /// Return whole macro call range if matched |
18 | Some(it) => it, | 18 | WholeCall, |
19 | }; | 19 | } |
20 | |||
21 | pub(crate) fn original_range_by_kind( | ||
22 | db: &RootDatabase, | ||
23 | node: InFile<&SyntaxNode>, | ||
24 | kind: OriginalRangeKind, | ||
25 | ) -> Option<FileRange> { | ||
26 | let expansion = node.file_id.expansion_info(db)?; | ||
27 | |||
28 | // the input node has only one token ? | ||
29 | let single = node.value.first_token()? == node.value.last_token()?; | ||
30 | |||
20 | // FIXME: We should handle recurside macro expansions | 31 | // FIXME: We should handle recurside macro expansions |
32 | let range = match kind { | ||
33 | OriginalRangeKind::WholeCall => expansion.call_node()?.map(|node| node.text_range()), | ||
34 | _ => node.value.descendants().find_map(|it| { | ||
35 | let first = it.first_token()?; | ||
36 | let last = it.last_token()?; | ||
21 | 37 | ||
22 | let range = node.value.descendants_with_tokens().find_map(|it| { | 38 | if !single && first == last { |
23 | match it.as_token() { | 39 | return None; |
24 | // FIXME: Remove this branch after all `tt::TokenTree`s have a proper `TokenId`, | ||
25 | // and return the range of the overall macro expansions if mapping first and last tokens fails. | ||
26 | Some(token) => { | ||
27 | let token = expansion.map_token_up(node.with_value(&token))?; | ||
28 | Some(token.with_value(token.value.text_range())) | ||
29 | } | 40 | } |
30 | None => { | ||
31 | // Try to map first and last tokens of node, and, if success, return the union range of mapped tokens | ||
32 | let n = it.into_node()?; | ||
33 | let first = expansion.map_token_up(node.with_value(&n.first_token()?))?; | ||
34 | let last = expansion.map_token_up(node.with_value(&n.last_token()?))?; | ||
35 | 41 | ||
36 | // FIXME: Is is possible ? | 42 | // Try to map first and last tokens of node, and, if success, return the union range of mapped tokens |
37 | if first.file_id != last.file_id { | 43 | let (first, first_origin) = expansion.map_token_up(node.with_value(&first))?; |
38 | return None; | 44 | let (last, last_origin) = expansion.map_token_up(node.with_value(&last))?; |
39 | } | ||
40 | 45 | ||
41 | // FIXME: Add union method in TextRange | 46 | if first.file_id != last.file_id |
42 | let range = union_range(first.value.text_range(), last.value.text_range()); | 47 | || first_origin != last_origin |
43 | Some(first.with_value(range)) | 48 | || (kind == OriginalRangeKind::CallToken && first_origin != ExpansionOrigin::Call) |
49 | { | ||
50 | return None; | ||
44 | } | 51 | } |
45 | } | ||
46 | }); | ||
47 | 52 | ||
48 | return match range { | 53 | // FIXME: Add union method in TextRange |
49 | Some(it) => FileRange { file_id: it.file_id.original_file(db), range: it.value }, | 54 | Some(first.with_value(union_range(first.value.text_range(), last.value.text_range()))) |
50 | None => { | 55 | })?, |
51 | FileRange { file_id: node.file_id.original_file(db), range: node.value.text_range() } | ||
52 | } | ||
53 | }; | 56 | }; |
54 | 57 | ||
58 | return Some(FileRange { file_id: range.file_id.original_file(db), range: range.value }); | ||
59 | |||
55 | fn union_range(a: TextRange, b: TextRange) -> TextRange { | 60 | fn union_range(a: TextRange, b: TextRange) -> TextRange { |
56 | let start = a.start().min(b.start()); | 61 | let start = a.start().min(b.start()); |
57 | let end = a.end().max(b.end()); | 62 | let end = a.end().max(b.end()); |
diff --git a/crates/ra_ide/src/goto_definition.rs b/crates/ra_ide/src/goto_definition.rs index cfe62037f..2c634990d 100644 --- a/crates/ra_ide/src/goto_definition.rs +++ b/crates/ra_ide/src/goto_definition.rs | |||
@@ -209,7 +209,7 @@ fn named_target(db: &RootDatabase, node: InFile<&SyntaxNode>) -> Option<Navigati | |||
209 | 209 | ||
210 | #[cfg(test)] | 210 | #[cfg(test)] |
211 | mod tests { | 211 | mod tests { |
212 | use test_utils::covers; | 212 | use test_utils::{assert_eq_text, covers}; |
213 | 213 | ||
214 | use crate::mock_analysis::analysis_and_position; | 214 | use crate::mock_analysis::analysis_and_position; |
215 | 215 | ||
@@ -222,6 +222,24 @@ mod tests { | |||
222 | nav.assert_match(expected); | 222 | nav.assert_match(expected); |
223 | } | 223 | } |
224 | 224 | ||
225 | fn check_goto_with_range_content(fixture: &str, expected: &str, expected_range: &str) { | ||
226 | let (analysis, pos) = analysis_and_position(fixture); | ||
227 | |||
228 | let mut navs = analysis.goto_definition(pos).unwrap().unwrap().info; | ||
229 | assert_eq!(navs.len(), 1); | ||
230 | let nav = navs.pop().unwrap(); | ||
231 | let file_text = analysis.file_text(pos.file_id).unwrap(); | ||
232 | |||
233 | let actual_full_range = &file_text[nav.full_range()]; | ||
234 | let actual_range = &file_text[nav.range()]; | ||
235 | |||
236 | test_utils::assert_eq_text!( | ||
237 | &format!("{}|{}", actual_full_range, actual_range), | ||
238 | expected_range | ||
239 | ); | ||
240 | nav.assert_match(expected); | ||
241 | } | ||
242 | |||
225 | #[test] | 243 | #[test] |
226 | fn goto_definition_works_in_items() { | 244 | fn goto_definition_works_in_items() { |
227 | check_goto( | 245 | check_goto( |
@@ -339,28 +357,27 @@ mod tests { | |||
339 | 357 | ||
340 | #[test] | 358 | #[test] |
341 | fn goto_definition_works_for_macro_defined_fn_with_arg() { | 359 | fn goto_definition_works_for_macro_defined_fn_with_arg() { |
342 | check_goto( | 360 | check_goto_with_range_content( |
343 | " | 361 | " |
344 | //- /lib.rs | 362 | //- /lib.rs |
345 | macro_rules! define_fn { | 363 | macro_rules! define_fn { |
346 | ($name:ident) => (fn $name() {}) | 364 | ($name:ident) => (fn $name() {}) |
347 | } | 365 | } |
348 | 366 | ||
349 | define_fn!( | 367 | define_fn!(foo); |
350 | foo | ||
351 | ) | ||
352 | 368 | ||
353 | fn bar() { | 369 | fn bar() { |
354 | <|>foo(); | 370 | <|>foo(); |
355 | } | 371 | } |
356 | ", | 372 | ", |
357 | "foo FN_DEF FileId(1) [80; 83) [80; 83)", | 373 | "foo FN_DEF FileId(1) [64; 80) [75; 78)", |
374 | "define_fn!(foo);|foo", | ||
358 | ); | 375 | ); |
359 | } | 376 | } |
360 | 377 | ||
361 | #[test] | 378 | #[test] |
362 | fn goto_definition_works_for_macro_defined_fn_no_arg() { | 379 | fn goto_definition_works_for_macro_defined_fn_no_arg() { |
363 | check_goto( | 380 | check_goto_with_range_content( |
364 | " | 381 | " |
365 | //- /lib.rs | 382 | //- /lib.rs |
366 | macro_rules! define_fn { | 383 | macro_rules! define_fn { |
@@ -373,7 +390,8 @@ mod tests { | |||
373 | <|>foo(); | 390 | <|>foo(); |
374 | } | 391 | } |
375 | ", | 392 | ", |
376 | "foo FN_DEF FileId(1) [39; 42) [39; 42)", | 393 | "foo FN_DEF FileId(1) [51; 64) [51; 64)", |
394 | "define_fn!();|define_fn!();", | ||
377 | ); | 395 | ); |
378 | } | 396 | } |
379 | 397 | ||