aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide_api
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide_api')
-rw-r--r--crates/ra_ide_api/src/goto_definition.rs87
-rw-r--r--crates/ra_ide_api/src/hover.rs4
-rw-r--r--crates/ra_ide_api/src/imp.rs26
-rw-r--r--crates/ra_ide_api/src/lib.rs124
-rw-r--r--crates/ra_ide_api/src/navigation_target.rs159
-rw-r--r--crates/ra_ide_api/src/parent_module.rs52
-rw-r--r--crates/ra_ide_api/tests/test/main.rs40
7 files changed, 255 insertions, 237 deletions
diff --git a/crates/ra_ide_api/src/goto_definition.rs b/crates/ra_ide_api/src/goto_definition.rs
index eaddd5083..8d2ff561a 100644
--- a/crates/ra_ide_api/src/goto_definition.rs
+++ b/crates/ra_ide_api/src/goto_definition.rs
@@ -1,22 +1,24 @@
1use ra_db::{FileId, Cancelable, SyntaxDatabase}; 1use ra_db::{FileId, Cancelable, SyntaxDatabase};
2use ra_syntax::{ 2use ra_syntax::{
3 TextRange, AstNode, ast, SyntaxKind::{NAME, MODULE}, 3 AstNode, ast,
4 algo::find_node_at_offset, 4 algo::find_node_at_offset,
5}; 5};
6 6
7use crate::{FilePosition, NavigationTarget, db::RootDatabase}; 7use crate::{FilePosition, NavigationTarget, db::RootDatabase, RangeInfo};
8 8
9pub(crate) fn goto_definition( 9pub(crate) fn goto_definition(
10 db: &RootDatabase, 10 db: &RootDatabase,
11 position: FilePosition, 11 position: FilePosition,
12) -> Cancelable<Option<Vec<NavigationTarget>>> { 12) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
13 let file = db.source_file(position.file_id); 13 let file = db.source_file(position.file_id);
14 let syntax = file.syntax(); 14 let syntax = file.syntax();
15 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(syntax, position.offset) { 15 if let Some(name_ref) = find_node_at_offset::<ast::NameRef>(syntax, position.offset) {
16 return Ok(Some(reference_definition(db, position.file_id, name_ref)?)); 16 let navs = reference_definition(db, position.file_id, name_ref)?;
17 return Ok(Some(RangeInfo::new(name_ref.syntax().range(), navs)));
17 } 18 }
18 if let Some(name) = find_node_at_offset::<ast::Name>(syntax, position.offset) { 19 if let Some(name) = find_node_at_offset::<ast::Name>(syntax, position.offset) {
19 return name_definition(db, position.file_id, name); 20 let navs = ctry!(name_definition(db, position.file_id, name)?);
21 return Ok(Some(RangeInfo::new(name.syntax().range(), navs)));
20 } 22 }
21 Ok(None) 23 Ok(None)
22} 24}
@@ -32,13 +34,7 @@ pub(crate) fn reference_definition(
32 let scope = fn_descr.scopes(db)?; 34 let scope = fn_descr.scopes(db)?;
33 // First try to resolve the symbol locally 35 // First try to resolve the symbol locally
34 if let Some(entry) = scope.resolve_local_name(name_ref) { 36 if let Some(entry) = scope.resolve_local_name(name_ref) {
35 let nav = NavigationTarget { 37 let nav = NavigationTarget::from_scope_entry(file_id, &entry);
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]); 38 return Ok(vec![nav]);
43 }; 39 };
44 } 40 }
@@ -79,18 +75,7 @@ fn name_definition(
79 if let Some(child_module) = 75 if let Some(child_module) =
80 hir::source_binder::module_from_declaration(db, file_id, module)? 76 hir::source_binder::module_from_declaration(db, file_id, module)?
81 { 77 {
82 let (file_id, _) = child_module.definition_source(db)?; 78 let nav = NavigationTarget::from_module(db, child_module)?;
83 let name = match child_module.name(db)? {
84 Some(name) => name.to_string().into(),
85 None => "".into(),
86 };
87 let nav = NavigationTarget {
88 file_id,
89 name,
90 range: TextRange::offset_len(0.into(), 0.into()),
91 kind: MODULE,
92 ptr: None,
93 };
94 return Ok(Some(vec![nav])); 79 return Ok(Some(vec![nav]));
95 } 80 }
96 } 81 }
@@ -100,31 +85,32 @@ fn name_definition(
100 85
101#[cfg(test)] 86#[cfg(test)]
102mod tests { 87mod tests {
103 use test_utils::assert_eq_dbg;
104 use crate::mock_analysis::analysis_and_position; 88 use crate::mock_analysis::analysis_and_position;
105 89
90 fn check_goto(fixuture: &str, expected: &str) {
91 let (analysis, pos) = analysis_and_position(fixuture);
92
93 let mut navs = analysis.goto_definition(pos).unwrap().unwrap().info;
94 assert_eq!(navs.len(), 1);
95 let nav = navs.pop().unwrap();
96 nav.assert_match(expected);
97 }
98
106 #[test] 99 #[test]
107 fn goto_definition_works_in_items() { 100 fn goto_definition_works_in_items() {
108 let (analysis, pos) = analysis_and_position( 101 check_goto(
109 " 102 "
110 //- /lib.rs 103 //- /lib.rs
111 struct Foo; 104 struct Foo;
112 enum E { X(Foo<|>) } 105 enum E { X(Foo<|>) }
113 ", 106 ",
114 ); 107 "Foo STRUCT_DEF FileId(1) [0; 11) [7; 10)",
115
116 let symbols = analysis.goto_definition(pos).unwrap().unwrap();
117 assert_eq_dbg(
118 r#"[NavigationTarget { file_id: FileId(1), name: "Foo",
119 kind: STRUCT_DEF, range: [0; 11),
120 ptr: Some(LocalSyntaxPtr { range: [0; 11), kind: STRUCT_DEF }) }]"#,
121 &symbols,
122 ); 108 );
123 } 109 }
124 110
125 #[test] 111 #[test]
126 fn goto_definition_resolves_correct_name() { 112 fn goto_definition_resolves_correct_name() {
127 let (analysis, pos) = analysis_and_position( 113 check_goto(
128 " 114 "
129 //- /lib.rs 115 //- /lib.rs
130 use a::Foo; 116 use a::Foo;
@@ -136,47 +122,30 @@ mod tests {
136 //- /b.rs 122 //- /b.rs
137 struct Foo; 123 struct Foo;
138 ", 124 ",
139 ); 125 "Foo STRUCT_DEF FileId(2) [0; 11) [7; 10)",
140
141 let symbols = analysis.goto_definition(pos).unwrap().unwrap();
142 assert_eq_dbg(
143 r#"[NavigationTarget { file_id: FileId(2), name: "Foo",
144 kind: STRUCT_DEF, range: [0; 11),
145 ptr: Some(LocalSyntaxPtr { range: [0; 11), kind: STRUCT_DEF }) }]"#,
146 &symbols,
147 ); 126 );
148 } 127 }
149 128
150 #[test] 129 #[test]
151 fn goto_definition_works_for_module_declaration() { 130 fn goto_definition_works_for_module_declaration() {
152 let (analysis, pos) = analysis_and_position( 131 check_goto(
153 " 132 "
154 //- /lib.rs 133 //- /lib.rs
155 mod <|>foo; 134 mod <|>foo;
156 //- /foo.rs 135 //- /foo.rs
157 // empty 136 // empty
158 ", 137 ",
159 ); 138 "foo SOURCE_FILE FileId(2) [0; 10)",
160
161 let symbols = analysis.goto_definition(pos).unwrap().unwrap();
162 assert_eq_dbg(
163 r#"[NavigationTarget { file_id: FileId(2), name: "foo", kind: MODULE, range: [0; 0), ptr: None }]"#,
164 &symbols,
165 ); 139 );
166 140
167 let (analysis, pos) = analysis_and_position( 141 check_goto(
168 " 142 "
169 //- /lib.rs 143 //- /lib.rs
170 mod <|>foo; 144 mod <|>foo;
171 //- /foo/mod.rs 145 //- /foo/mod.rs
172 // empty 146 // empty
173 ", 147 ",
174 ); 148 "foo SOURCE_FILE FileId(2) [0; 10)",
175
176 let symbols = analysis.goto_definition(pos).unwrap().unwrap();
177 assert_eq_dbg(
178 r#"[NavigationTarget { file_id: FileId(2), name: "foo", kind: MODULE, range: [0; 0), ptr: None }]"#,
179 &symbols,
180 ); 149 );
181 } 150 }
182} 151}
diff --git a/crates/ra_ide_api/src/hover.rs b/crates/ra_ide_api/src/hover.rs
index 41309e756..f544ffa6d 100644
--- a/crates/ra_ide_api/src/hover.rs
+++ b/crates/ra_ide_api/src/hover.rs
@@ -88,11 +88,11 @@ fn doc_text_for(db: &RootDatabase, nav: NavigationTarget) -> Cancelable<Option<S
88 88
89impl NavigationTarget { 89impl NavigationTarget {
90 fn node(&self, db: &RootDatabase) -> Option<TreePtr<SyntaxNode>> { 90 fn node(&self, db: &RootDatabase) -> Option<TreePtr<SyntaxNode>> {
91 let source_file = db.source_file(self.file_id); 91 let source_file = db.source_file(self.file_id());
92 let source_file = source_file.syntax(); 92 let source_file = source_file.syntax();
93 let node = source_file 93 let node = source_file
94 .descendants() 94 .descendants()
95 .find(|node| node.kind() == self.kind && node.range() == self.range)? 95 .find(|node| node.kind() == self.kind() && node.range() == self.full_range())?
96 .to_owned(); 96 .to_owned();
97 Some(node) 97 Some(node)
98 } 98 }
diff --git a/crates/ra_ide_api/src/imp.rs b/crates/ra_ide_api/src/imp.rs
index 7c60ab7d6..ba4aa0fd5 100644
--- a/crates/ra_ide_api/src/imp.rs
+++ b/crates/ra_ide_api/src/imp.rs
@@ -11,12 +11,11 @@ use ra_syntax::{
11 TextRange, AstNode, SourceFile, 11 TextRange, AstNode, SourceFile,
12 ast::{self, NameOwner}, 12 ast::{self, NameOwner},
13 algo::find_node_at_offset, 13 algo::find_node_at_offset,
14 SyntaxKind::*,
15}; 14};
16 15
17use crate::{ 16use crate::{
18 AnalysisChange, 17 AnalysisChange,
19 Cancelable, NavigationTarget, 18 Cancelable,
20 CrateId, db, Diagnostic, FileId, FilePosition, FileRange, FileSystemEdit, 19 CrateId, db, Diagnostic, FileId, FilePosition, FileRange, FileSystemEdit,
21 Query, RootChange, SourceChange, SourceFileEdit, 20 Query, RootChange, SourceChange, SourceFileEdit,
22 symbol_index::{LibrarySymbolsQuery, FileSymbol}, 21 symbol_index::{LibrarySymbolsQuery, FileSymbol},
@@ -99,29 +98,6 @@ impl db::RootDatabase {
99} 98}
100 99
101impl db::RootDatabase { 100impl db::RootDatabase {
102 /// This returns `Vec` because a module may be included from several places. We
103 /// don't handle this case yet though, so the Vec has length at most one.
104 pub(crate) fn parent_module(
105 &self,
106 position: FilePosition,
107 ) -> Cancelable<Vec<NavigationTarget>> {
108 let module = match source_binder::module_from_position(self, position)? {
109 None => return Ok(Vec::new()),
110 Some(it) => it,
111 };
112 let (file_id, ast_module) = match module.declaration_source(self)? {
113 None => return Ok(Vec::new()),
114 Some(it) => it,
115 };
116 let name = ast_module.name().unwrap();
117 Ok(vec![NavigationTarget {
118 file_id,
119 name: name.text().clone(),
120 range: name.syntax().range(),
121 kind: MODULE,
122 ptr: None,
123 }])
124 }
125 /// Returns `Vec` for the same reason as `parent_module` 101 /// Returns `Vec` for the same reason as `parent_module`
126 pub(crate) fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> { 102 pub(crate) fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> {
127 let module = match source_binder::module_from_file_id(self, file_id)? { 103 let module = match source_binder::module_from_file_id(self, file_id)? {
diff --git a/crates/ra_ide_api/src/lib.rs b/crates/ra_ide_api/src/lib.rs
index 65d21d899..6155d903a 100644
--- a/crates/ra_ide_api/src/lib.rs
+++ b/crates/ra_ide_api/src/lib.rs
@@ -18,25 +18,26 @@ macro_rules! ctry {
18 }; 18 };
19} 19}
20 20
21mod completion;
22mod db; 21mod db;
23mod goto_definition;
24mod imp; 22mod imp;
25pub mod mock_analysis; 23pub mod mock_analysis;
26mod runnables;
27mod symbol_index; 24mod symbol_index;
25mod navigation_target;
28 26
27mod completion;
28mod runnables;
29mod goto_definition;
29mod extend_selection; 30mod extend_selection;
30mod hover; 31mod hover;
31mod call_info; 32mod call_info;
32mod syntax_highlighting; 33mod syntax_highlighting;
34mod parent_module;
33 35
34use std::{fmt, sync::Arc}; 36use std::{fmt, sync::Arc};
35 37
36use hir::{Def, ModuleSource, Name}; 38use ra_syntax::{SourceFile, TreePtr, TextRange, TextUnit};
37use ra_syntax::{SmolStr, SourceFile, TreePtr, SyntaxKind, SyntaxNode, TextRange, TextUnit, AstNode};
38use ra_text_edit::TextEdit; 39use ra_text_edit::TextEdit;
39use ra_db::{SyntaxDatabase, FilesDatabase, LocalSyntaxPtr, BaseDatabase}; 40use ra_db::{SyntaxDatabase, FilesDatabase, BaseDatabase};
40use rayon::prelude::*; 41use rayon::prelude::*;
41use relative_path::RelativePathBuf; 42use relative_path::RelativePathBuf;
42use rustc_hash::FxHashMap; 43use rustc_hash::FxHashMap;
@@ -50,6 +51,7 @@ use crate::{
50pub use crate::{ 51pub use crate::{
51 completion::{CompletionItem, CompletionItemKind, InsertText}, 52 completion::{CompletionItem, CompletionItemKind, InsertText},
52 runnables::{Runnable, RunnableKind}, 53 runnables::{Runnable, RunnableKind},
54 navigation_target::NavigationTarget,
53}; 55};
54pub use ra_ide_api_light::{ 56pub use ra_ide_api_light::{
55 Fold, FoldKind, HighlightedRange, Severity, StructureNode, 57 Fold, FoldKind, HighlightedRange, Severity, StructureNode,
@@ -243,110 +245,6 @@ impl Query {
243 } 245 }
244} 246}
245 247
246/// `NavigationTarget` represents and element in the editor's UI whihc you can
247/// click on to navigate to a particular piece of code.
248///
249/// Typically, a `NavigationTarget` corresponds to some element in the source
250/// code, like a function or a struct, but this is not strictly required.
251#[derive(Debug, Clone)]
252pub struct NavigationTarget {
253 file_id: FileId,
254 name: SmolStr,
255 kind: SyntaxKind,
256 range: TextRange,
257 // Should be DefId ideally
258 ptr: Option<LocalSyntaxPtr>,
259}
260
261impl NavigationTarget {
262 fn from_symbol(symbol: FileSymbol) -> NavigationTarget {
263 NavigationTarget {
264 file_id: symbol.file_id,
265 name: symbol.name.clone(),
266 kind: symbol.ptr.kind(),
267 range: symbol.ptr.range(),
268 ptr: Some(symbol.ptr.clone()),
269 }
270 }
271
272 fn from_syntax(name: Option<Name>, file_id: FileId, node: &SyntaxNode) -> NavigationTarget {
273 NavigationTarget {
274 file_id,
275 name: name.map(|n| n.to_string().into()).unwrap_or("".into()),
276 kind: node.kind(),
277 range: node.range(),
278 ptr: Some(LocalSyntaxPtr::new(node)),
279 }
280 }
281 // TODO once Def::Item is gone, this should be able to always return a NavigationTarget
282 fn from_def(db: &db::RootDatabase, def: Def) -> Cancelable<Option<NavigationTarget>> {
283 Ok(match def {
284 Def::Struct(s) => {
285 let (file_id, node) = s.source(db)?;
286 Some(NavigationTarget::from_syntax(
287 s.name(db)?,
288 file_id.original_file(db),
289 node.syntax(),
290 ))
291 }
292 Def::Enum(e) => {
293 let (file_id, node) = e.source(db)?;
294 Some(NavigationTarget::from_syntax(
295 e.name(db)?,
296 file_id.original_file(db),
297 node.syntax(),
298 ))
299 }
300 Def::EnumVariant(ev) => {
301 let (file_id, node) = ev.source(db)?;
302 Some(NavigationTarget::from_syntax(
303 ev.name(db)?,
304 file_id.original_file(db),
305 node.syntax(),
306 ))
307 }
308 Def::Function(f) => {
309 let (file_id, node) = f.source(db)?;
310 let name = f.signature(db).name().clone();
311 Some(NavigationTarget::from_syntax(
312 Some(name),
313 file_id.original_file(db),
314 node.syntax(),
315 ))
316 }
317 Def::Module(m) => {
318 let (file_id, source) = m.definition_source(db)?;
319 let name = m.name(db)?;
320 match source {
321 ModuleSource::SourceFile(node) => {
322 Some(NavigationTarget::from_syntax(name, file_id, node.syntax()))
323 }
324 ModuleSource::Module(node) => {
325 Some(NavigationTarget::from_syntax(name, file_id, node.syntax()))
326 }
327 }
328 }
329 Def::Item => None,
330 })
331 }
332
333 pub fn name(&self) -> &SmolStr {
334 &self.name
335 }
336
337 pub fn kind(&self) -> SyntaxKind {
338 self.kind
339 }
340
341 pub fn file_id(&self) -> FileId {
342 self.file_id
343 }
344
345 pub fn range(&self) -> TextRange {
346 self.range
347 }
348}
349
350#[derive(Debug)] 248#[derive(Debug)]
351pub struct RangeInfo<T> { 249pub struct RangeInfo<T> {
352 pub range: TextRange, 250 pub range: TextRange,
@@ -354,7 +252,7 @@ pub struct RangeInfo<T> {
354} 252}
355 253
356impl<T> RangeInfo<T> { 254impl<T> RangeInfo<T> {
357 fn new(range: TextRange, info: T) -> RangeInfo<T> { 255 pub fn new(range: TextRange, info: T) -> RangeInfo<T> {
358 RangeInfo { range, info } 256 RangeInfo { range, info }
359 } 257 }
360} 258}
@@ -494,7 +392,7 @@ impl Analysis {
494 pub fn goto_definition( 392 pub fn goto_definition(
495 &self, 393 &self,
496 position: FilePosition, 394 position: FilePosition,
497 ) -> Cancelable<Option<Vec<NavigationTarget>>> { 395 ) -> Cancelable<Option<RangeInfo<Vec<NavigationTarget>>>> {
498 self.db 396 self.db
499 .catch_canceled(|db| goto_definition::goto_definition(db, position))? 397 .catch_canceled(|db| goto_definition::goto_definition(db, position))?
500 } 398 }
@@ -517,7 +415,7 @@ impl Analysis {
517 415
518 /// Returns a `mod name;` declaration which created the current module. 416 /// Returns a `mod name;` declaration which created the current module.
519 pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<NavigationTarget>> { 417 pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<NavigationTarget>> {
520 self.with_db(|db| db.parent_module(position))? 418 self.with_db(|db| parent_module::parent_module(db, position))?
521 } 419 }
522 420
523 /// Returns crates this file belongs too. 421 /// Returns crates this file belongs too.
diff --git a/crates/ra_ide_api/src/navigation_target.rs b/crates/ra_ide_api/src/navigation_target.rs
new file mode 100644
index 000000000..b955bbe42
--- /dev/null
+++ b/crates/ra_ide_api/src/navigation_target.rs
@@ -0,0 +1,159 @@
1use ra_db::{FileId, Cancelable};
2use ra_syntax::{
3 SyntaxNode, AstNode, SmolStr, TextRange, ast,
4 SyntaxKind::{self, NAME},
5};
6use hir::{Def, ModuleSource};
7
8use crate::{FileSymbol, db::RootDatabase};
9
10/// `NavigationTarget` represents and element in the editor's UI which you can
11/// click on to navigate to a particular piece of code.
12///
13/// Typically, a `NavigationTarget` corresponds to some element in the source
14/// code, like a function or a struct, but this is not strictly required.
15#[derive(Debug, Clone)]
16pub struct NavigationTarget {
17 file_id: FileId,
18 name: SmolStr,
19 kind: SyntaxKind,
20 full_range: TextRange,
21 focus_range: Option<TextRange>,
22}
23
24impl NavigationTarget {
25 pub fn name(&self) -> &SmolStr {
26 &self.name
27 }
28
29 pub fn kind(&self) -> SyntaxKind {
30 self.kind
31 }
32
33 pub fn file_id(&self) -> FileId {
34 self.file_id
35 }
36
37 pub fn full_range(&self) -> TextRange {
38 self.full_range
39 }
40
41 /// A "most interesting" range withing the `range_full`.
42 ///
43 /// Typically, `range` is the whole syntax node, including doc comments, and
44 /// `focus_range` is the range of the identifier.
45 pub fn focus_range(&self) -> Option<TextRange> {
46 self.focus_range
47 }
48
49 pub(crate) fn from_symbol(symbol: FileSymbol) -> NavigationTarget {
50 NavigationTarget {
51 file_id: symbol.file_id,
52 name: symbol.name.clone(),
53 kind: symbol.ptr.kind(),
54 full_range: symbol.ptr.range(),
55 focus_range: None,
56 }
57 }
58
59 pub(crate) fn from_scope_entry(
60 file_id: FileId,
61 entry: &hir::ScopeEntryWithSyntax,
62 ) -> NavigationTarget {
63 NavigationTarget {
64 file_id,
65 name: entry.name().to_string().into(),
66 full_range: entry.ptr().range(),
67 focus_range: None,
68 kind: NAME,
69 }
70 }
71
72 pub(crate) fn from_module(
73 db: &RootDatabase,
74 module: hir::Module,
75 ) -> Cancelable<NavigationTarget> {
76 let (file_id, source) = module.definition_source(db)?;
77 let name = module
78 .name(db)?
79 .map(|it| it.to_string().into())
80 .unwrap_or_default();
81 let res = match source {
82 ModuleSource::SourceFile(node) => {
83 NavigationTarget::from_syntax(file_id, name, None, node.syntax())
84 }
85 ModuleSource::Module(node) => {
86 NavigationTarget::from_syntax(file_id, name, None, node.syntax())
87 }
88 };
89 Ok(res)
90 }
91
92 // TODO once Def::Item is gone, this should be able to always return a NavigationTarget
93 pub(crate) fn from_def(db: &RootDatabase, def: Def) -> Cancelable<Option<NavigationTarget>> {
94 let res = match def {
95 Def::Struct(s) => {
96 let (file_id, node) = s.source(db)?;
97 NavigationTarget::from_named(file_id.original_file(db), &*node)
98 }
99 Def::Enum(e) => {
100 let (file_id, node) = e.source(db)?;
101 NavigationTarget::from_named(file_id.original_file(db), &*node)
102 }
103 Def::EnumVariant(ev) => {
104 let (file_id, node) = ev.source(db)?;
105 NavigationTarget::from_named(file_id.original_file(db), &*node)
106 }
107 Def::Function(f) => {
108 let (file_id, node) = f.source(db)?;
109 NavigationTarget::from_named(file_id.original_file(db), &*node)
110 }
111 Def::Module(m) => NavigationTarget::from_module(db, m)?,
112 Def::Item => return Ok(None),
113 };
114 Ok(Some(res))
115 }
116
117 #[cfg(test)]
118 pub(crate) fn assert_match(&self, expected: &str) {
119 let actual = self.debug_render();
120 test_utils::assert_eq_text!(expected.trim(), actual.trim(),);
121 }
122
123 #[cfg(test)]
124 pub(crate) fn debug_render(&self) -> String {
125 let mut buf = format!(
126 "{} {:?} {:?} {:?}",
127 self.name(),
128 self.kind(),
129 self.file_id(),
130 self.full_range()
131 );
132 if let Some(focus_range) = self.focus_range() {
133 buf.push_str(&format!(" {:?}", focus_range))
134 }
135 buf
136 }
137
138 fn from_named(file_id: FileId, node: &impl ast::NameOwner) -> NavigationTarget {
139 let name = node.name().map(|it| it.text().clone()).unwrap_or_default();
140 let focus_range = node.name().map(|it| it.syntax().range());
141 NavigationTarget::from_syntax(file_id, name, focus_range, node.syntax())
142 }
143
144 fn from_syntax(
145 file_id: FileId,
146 name: SmolStr,
147 focus_range: Option<TextRange>,
148 node: &SyntaxNode,
149 ) -> NavigationTarget {
150 NavigationTarget {
151 file_id,
152 name,
153 kind: node.kind(),
154 full_range: node.range(),
155 focus_range,
156 // ptr: Some(LocalSyntaxPtr::new(node)),
157 }
158 }
159}
diff --git a/crates/ra_ide_api/src/parent_module.rs b/crates/ra_ide_api/src/parent_module.rs
new file mode 100644
index 000000000..d345839a3
--- /dev/null
+++ b/crates/ra_ide_api/src/parent_module.rs
@@ -0,0 +1,52 @@
1use ra_db::{Cancelable, FilePosition};
2
3use crate::{NavigationTarget, db::RootDatabase};
4
5/// This returns `Vec` because a module may be included from several places. We
6/// don't handle this case yet though, so the Vec has length at most one.
7pub(crate) fn parent_module(
8 db: &RootDatabase,
9 position: FilePosition,
10) -> Cancelable<Vec<NavigationTarget>> {
11 let module = match hir::source_binder::module_from_position(db, position)? {
12 None => return Ok(Vec::new()),
13 Some(it) => it,
14 };
15 let nav = NavigationTarget::from_module(db, module)?;
16 Ok(vec![nav])
17}
18
19#[cfg(test)]
20mod tests {
21 use crate::mock_analysis::analysis_and_position;
22
23 #[test]
24 fn test_resolve_parent_module() {
25 let (analysis, pos) = analysis_and_position(
26 "
27 //- /lib.rs
28 mod foo;
29 //- /foo.rs
30 <|>// empty
31 ",
32 );
33 let nav = analysis.parent_module(pos).unwrap().pop().unwrap();
34 nav.assert_match("foo SOURCE_FILE FileId(2) [0; 10)");
35 }
36
37 #[test]
38 fn test_resolve_parent_module_for_inline() {
39 let (analysis, pos) = analysis_and_position(
40 "
41 //- /lib.rs
42 mod foo {
43 mod bar {
44 mod baz { <|> }
45 }
46 }
47 ",
48 );
49 let nav = analysis.parent_module(pos).unwrap().pop().unwrap();
50 nav.assert_match("baz MODULE FileId(1) [32; 44)");
51 }
52}
diff --git a/crates/ra_ide_api/tests/test/main.rs b/crates/ra_ide_api/tests/test/main.rs
index d1dc07e5b..7dc1dba73 100644
--- a/crates/ra_ide_api/tests/test/main.rs
+++ b/crates/ra_ide_api/tests/test/main.rs
@@ -4,7 +4,7 @@ use ra_syntax::TextRange;
4use test_utils::{assert_eq_dbg, assert_eq_text}; 4use test_utils::{assert_eq_dbg, assert_eq_text};
5 5
6use ra_ide_api::{ 6use ra_ide_api::{
7 mock_analysis::{analysis_and_position, single_file, single_file_with_position, MockAnalysis}, 7 mock_analysis::{single_file, single_file_with_position, MockAnalysis},
8 AnalysisChange, CrateGraph, FileId, Query 8 AnalysisChange, CrateGraph, FileId, Query
9}; 9};
10 10
@@ -35,42 +35,6 @@ fn test_unresolved_module_diagnostic_no_diag_for_inline_mode() {
35} 35}
36 36
37#[test] 37#[test]
38fn test_resolve_parent_module() {
39 let (analysis, pos) = analysis_and_position(
40 "
41 //- /lib.rs
42 mod foo;
43 //- /foo.rs
44 <|>// empty
45 ",
46 );
47 let symbols = analysis.parent_module(pos).unwrap();
48 assert_eq_dbg(
49 r#"[NavigationTarget { file_id: FileId(1), name: "foo", kind: MODULE, range: [4; 7), ptr: None }]"#,
50 &symbols,
51 );
52}
53
54#[test]
55fn test_resolve_parent_module_for_inline() {
56 let (analysis, pos) = analysis_and_position(
57 "
58 //- /lib.rs
59 mod foo {
60 mod bar {
61 mod baz { <|> }
62 }
63 }
64 ",
65 );
66 let symbols = analysis.parent_module(pos).unwrap();
67 assert_eq_dbg(
68 r#"[NavigationTarget { file_id: FileId(1), name: "baz", kind: MODULE, range: [36; 39), ptr: None }]"#,
69 &symbols,
70 );
71}
72
73#[test]
74fn test_resolve_crate_root() { 38fn test_resolve_crate_root() {
75 let mock = MockAnalysis::with_files( 39 let mock = MockAnalysis::with_files(
76 " 40 "
@@ -245,5 +209,5 @@ pub trait HirDatabase: SyntaxDatabase {}
245 let mut symbols = analysis.symbol_search(Query::new("Hir".into())).unwrap(); 209 let mut symbols = analysis.symbol_search(Query::new("Hir".into())).unwrap();
246 let s = symbols.pop().unwrap(); 210 let s = symbols.pop().unwrap();
247 assert_eq!(s.name(), "HirDatabase"); 211 assert_eq!(s.name(), "HirDatabase");
248 assert_eq!(s.range(), TextRange::from_to(33.into(), 44.into())); 212 assert_eq!(s.full_range(), TextRange::from_to(33.into(), 44.into()));
249} 213}