diff options
Diffstat (limited to 'crates/ra_ide_api/src/goto_defenition.rs')
-rw-r--r-- | crates/ra_ide_api/src/goto_defenition.rs | 139 |
1 files changed, 139 insertions, 0 deletions
diff --git a/crates/ra_ide_api/src/goto_defenition.rs b/crates/ra_ide_api/src/goto_defenition.rs new file mode 100644 index 000000000..fcd8d315e --- /dev/null +++ b/crates/ra_ide_api/src/goto_defenition.rs | |||
@@ -0,0 +1,139 @@ | |||
1 | use ra_db::{FileId, Cancelable, SyntaxDatabase}; | ||
2 | use ra_syntax::{ | ||
3 | TextRange, AstNode, ast, SyntaxKind::{NAME, MODULE}, | ||
4 | algo::find_node_at_offset, | ||
5 | }; | ||
6 | |||
7 | use crate::{FilePosition, NavigationTarget, db::RootDatabase}; | ||
8 | |||
9 | pub(crate) fn goto_defenition( | ||
10 | db: &RootDatabase, | ||
11 | position: FilePosition, | ||
12 | ) -> Cancelable<Option<Vec<NavigationTarget>>> { | ||
13 | let file = db.source_file(position.file_id); | ||
14 | let syntax = file.syntax(); | ||
15 | if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(syntax, position.offset) { | ||
16 | return Ok(Some(reference_defenition(db, position.file_id, name_ref)?)); | ||
17 | } | ||
18 | if let Some(name) = find_node_at_offset::<ast::Name>(syntax, position.offset) { | ||
19 | return name_defenition(db, position.file_id, name); | ||
20 | } | ||
21 | Ok(None) | ||
22 | } | ||
23 | |||
24 | pub(crate) fn reference_defenition( | ||
25 | db: &RootDatabase, | ||
26 | file_id: FileId, | ||
27 | name_ref: &ast::NameRef, | ||
28 | ) -> Cancelable<Vec<NavigationTarget>> { | ||
29 | if let Some(fn_descr) = | ||
30 | hir::source_binder::function_from_child_node(db, file_id, name_ref.syntax())? | ||
31 | { | ||
32 | let scope = fn_descr.scopes(db)?; | ||
33 | // First try to resolve the symbol locally | ||
34 | if let Some(entry) = scope.resolve_local_name(name_ref) { | ||
35 | let nav = NavigationTarget { | ||
36 | file_id, | ||
37 | name: entry.name().to_string().into(), | ||
38 | range: entry.ptr().range(), | ||
39 | kind: NAME, | ||
40 | ptr: None, | ||
41 | }; | ||
42 | return Ok(vec![nav]); | ||
43 | }; | ||
44 | } | ||
45 | // If that fails try the index based approach. | ||
46 | let navs = db | ||
47 | .index_resolve(name_ref)? | ||
48 | .into_iter() | ||
49 | .map(NavigationTarget::from_symbol) | ||
50 | .collect(); | ||
51 | Ok(navs) | ||
52 | } | ||
53 | |||
54 | fn name_defenition( | ||
55 | db: &RootDatabase, | ||
56 | file_id: FileId, | ||
57 | name: &ast::Name, | ||
58 | ) -> Cancelable<Option<Vec<NavigationTarget>>> { | ||
59 | if let Some(module) = name.syntax().parent().and_then(ast::Module::cast) { | ||
60 | if module.has_semi() { | ||
61 | if let Some(child_module) = | ||
62 | hir::source_binder::module_from_declaration(db, file_id, module)? | ||
63 | { | ||
64 | let (file_id, _) = child_module.defenition_source(db)?; | ||
65 | let name = match child_module.name(db)? { | ||
66 | Some(name) => name.to_string().into(), | ||
67 | None => "".into(), | ||
68 | }; | ||
69 | let nav = NavigationTarget { | ||
70 | file_id, | ||
71 | name, | ||
72 | range: TextRange::offset_len(0.into(), 0.into()), | ||
73 | kind: MODULE, | ||
74 | ptr: None, | ||
75 | }; | ||
76 | return Ok(Some(vec![nav])); | ||
77 | } | ||
78 | } | ||
79 | } | ||
80 | Ok(None) | ||
81 | } | ||
82 | |||
83 | #[cfg(test)] | ||
84 | mod tests { | ||
85 | use test_utils::assert_eq_dbg; | ||
86 | use crate::mock_analysis::analysis_and_position; | ||
87 | |||
88 | #[test] | ||
89 | fn goto_defenition_works_in_items() { | ||
90 | let (analysis, pos) = analysis_and_position( | ||
91 | " | ||
92 | //- /lib.rs | ||
93 | struct Foo; | ||
94 | enum E { X(Foo<|>) } | ||
95 | ", | ||
96 | ); | ||
97 | |||
98 | let symbols = analysis.goto_defenition(pos).unwrap().unwrap(); | ||
99 | assert_eq_dbg( | ||
100 | r#"[NavigationTarget { file_id: FileId(1), name: "Foo", | ||
101 | kind: STRUCT_DEF, range: [0; 11), | ||
102 | ptr: Some(LocalSyntaxPtr { range: [0; 11), kind: STRUCT_DEF }) }]"#, | ||
103 | &symbols, | ||
104 | ); | ||
105 | } | ||
106 | |||
107 | #[test] | ||
108 | fn goto_defenition_works_for_module_declaration() { | ||
109 | let (analysis, pos) = analysis_and_position( | ||
110 | " | ||
111 | //- /lib.rs | ||
112 | mod <|>foo; | ||
113 | //- /foo.rs | ||
114 | // empty | ||
115 | ", | ||
116 | ); | ||
117 | |||
118 | let symbols = analysis.goto_defenition(pos).unwrap().unwrap(); | ||
119 | assert_eq_dbg( | ||
120 | r#"[NavigationTarget { file_id: FileId(2), name: "foo", kind: MODULE, range: [0; 0), ptr: None }]"#, | ||
121 | &symbols, | ||
122 | ); | ||
123 | |||
124 | let (analysis, pos) = analysis_and_position( | ||
125 | " | ||
126 | //- /lib.rs | ||
127 | mod <|>foo; | ||
128 | //- /foo/mod.rs | ||
129 | // empty | ||
130 | ", | ||
131 | ); | ||
132 | |||
133 | let symbols = analysis.goto_defenition(pos).unwrap().unwrap(); | ||
134 | assert_eq_dbg( | ||
135 | r#"[NavigationTarget { file_id: FileId(2), name: "foo", kind: MODULE, range: [0; 0), ptr: None }]"#, | ||
136 | &symbols, | ||
137 | ); | ||
138 | } | ||
139 | } | ||