diff options
-rw-r--r-- | crates/ra_hir/src/code_model_api.rs | 2 | ||||
-rw-r--r-- | crates/ra_hir/src/code_model_impl/function.rs | 2 | ||||
-rw-r--r-- | crates/ra_hir/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_ide_api/src/goto_definition.rs | 87 | ||||
-rw-r--r-- | crates/ra_ide_api/src/hover.rs | 4 | ||||
-rw-r--r-- | crates/ra_ide_api/src/imp.rs | 26 | ||||
-rw-r--r-- | crates/ra_ide_api/src/lib.rs | 124 | ||||
-rw-r--r-- | crates/ra_ide_api/src/navigation_target.rs | 159 | ||||
-rw-r--r-- | crates/ra_ide_api/src/parent_module.rs | 52 | ||||
-rw-r--r-- | crates/ra_ide_api/tests/test/main.rs | 40 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/conv.rs | 28 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 16 |
12 files changed, 294 insertions, 248 deletions
diff --git a/crates/ra_hir/src/code_model_api.rs b/crates/ra_hir/src/code_model_api.rs index e69f546ff..8ec6b9b2b 100644 --- a/crates/ra_hir/src/code_model_api.rs +++ b/crates/ra_hir/src/code_model_api.rs | |||
@@ -274,6 +274,8 @@ pub struct Function { | |||
274 | pub(crate) def_id: DefId, | 274 | pub(crate) def_id: DefId, |
275 | } | 275 | } |
276 | 276 | ||
277 | pub use crate::code_model_impl::function::ScopeEntryWithSyntax; | ||
278 | |||
277 | /// The declared signature of a function. | 279 | /// The declared signature of a function. |
278 | #[derive(Debug, Clone, PartialEq, Eq)] | 280 | #[derive(Debug, Clone, PartialEq, Eq)] |
279 | pub struct FnSignature { | 281 | pub struct FnSignature { |
diff --git a/crates/ra_hir/src/code_model_impl/function.rs b/crates/ra_hir/src/code_model_impl/function.rs index 1bd4cc802..009175bab 100644 --- a/crates/ra_hir/src/code_model_impl/function.rs +++ b/crates/ra_hir/src/code_model_impl/function.rs | |||
@@ -15,7 +15,7 @@ use crate::{ | |||
15 | impl_block::ImplBlock, | 15 | impl_block::ImplBlock, |
16 | }; | 16 | }; |
17 | 17 | ||
18 | pub use self::scope::{FnScopes, ScopesWithSyntaxMapping}; | 18 | pub use self::scope::{FnScopes, ScopesWithSyntaxMapping, ScopeEntryWithSyntax}; |
19 | 19 | ||
20 | impl Function { | 20 | impl Function { |
21 | pub(crate) fn new(def_id: DefId) -> Function { | 21 | pub(crate) fn new(def_id: DefId) -> Function { |
diff --git a/crates/ra_hir/src/lib.rs b/crates/ra_hir/src/lib.rs index 1aca2f067..fe8be5700 100644 --- a/crates/ra_hir/src/lib.rs +++ b/crates/ra_hir/src/lib.rs | |||
@@ -59,5 +59,5 @@ pub use self::code_model_api::{ | |||
59 | Def, | 59 | Def, |
60 | Module, ModuleSource, Problem, | 60 | Module, ModuleSource, Problem, |
61 | Struct, Enum, EnumVariant, | 61 | Struct, Enum, EnumVariant, |
62 | Function, FnSignature, | 62 | Function, FnSignature, ScopeEntryWithSyntax, |
63 | }; | 63 | }; |
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 @@ | |||
1 | use ra_db::{FileId, Cancelable, SyntaxDatabase}; | 1 | use ra_db::{FileId, Cancelable, SyntaxDatabase}; |
2 | use ra_syntax::{ | 2 | use 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 | ||
7 | use crate::{FilePosition, NavigationTarget, db::RootDatabase}; | 7 | use crate::{FilePosition, NavigationTarget, db::RootDatabase, RangeInfo}; |
8 | 8 | ||
9 | pub(crate) fn goto_definition( | 9 | pub(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)] |
102 | mod tests { | 87 | mod 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 | ||
89 | impl NavigationTarget { | 89 | impl 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 | ||
17 | use crate::{ | 16 | use 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 | ||
101 | impl db::RootDatabase { | 100 | impl 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 | ||
21 | mod completion; | ||
22 | mod db; | 21 | mod db; |
23 | mod goto_definition; | ||
24 | mod imp; | 22 | mod imp; |
25 | pub mod mock_analysis; | 23 | pub mod mock_analysis; |
26 | mod runnables; | ||
27 | mod symbol_index; | 24 | mod symbol_index; |
25 | mod navigation_target; | ||
28 | 26 | ||
27 | mod completion; | ||
28 | mod runnables; | ||
29 | mod goto_definition; | ||
29 | mod extend_selection; | 30 | mod extend_selection; |
30 | mod hover; | 31 | mod hover; |
31 | mod call_info; | 32 | mod call_info; |
32 | mod syntax_highlighting; | 33 | mod syntax_highlighting; |
34 | mod parent_module; | ||
33 | 35 | ||
34 | use std::{fmt, sync::Arc}; | 36 | use std::{fmt, sync::Arc}; |
35 | 37 | ||
36 | use hir::{Def, ModuleSource, Name}; | 38 | use ra_syntax::{SourceFile, TreePtr, TextRange, TextUnit}; |
37 | use ra_syntax::{SmolStr, SourceFile, TreePtr, SyntaxKind, SyntaxNode, TextRange, TextUnit, AstNode}; | ||
38 | use ra_text_edit::TextEdit; | 39 | use ra_text_edit::TextEdit; |
39 | use ra_db::{SyntaxDatabase, FilesDatabase, LocalSyntaxPtr, BaseDatabase}; | 40 | use ra_db::{SyntaxDatabase, FilesDatabase, BaseDatabase}; |
40 | use rayon::prelude::*; | 41 | use rayon::prelude::*; |
41 | use relative_path::RelativePathBuf; | 42 | use relative_path::RelativePathBuf; |
42 | use rustc_hash::FxHashMap; | 43 | use rustc_hash::FxHashMap; |
@@ -50,6 +51,7 @@ use crate::{ | |||
50 | pub use crate::{ | 51 | pub 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 | }; |
54 | pub use ra_ide_api_light::{ | 56 | pub 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)] | ||
252 | pub 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 | |||
261 | impl 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)] |
351 | pub struct RangeInfo<T> { | 249 | pub struct RangeInfo<T> { |
352 | pub range: TextRange, | 250 | pub range: TextRange, |
@@ -354,7 +252,7 @@ pub struct RangeInfo<T> { | |||
354 | } | 252 | } |
355 | 253 | ||
356 | impl<T> RangeInfo<T> { | 254 | impl<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 @@ | |||
1 | use ra_db::{FileId, Cancelable}; | ||
2 | use ra_syntax::{ | ||
3 | SyntaxNode, AstNode, SmolStr, TextRange, ast, | ||
4 | SyntaxKind::{self, NAME}, | ||
5 | }; | ||
6 | use hir::{Def, ModuleSource}; | ||
7 | |||
8 | use 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)] | ||
16 | pub struct NavigationTarget { | ||
17 | file_id: FileId, | ||
18 | name: SmolStr, | ||
19 | kind: SyntaxKind, | ||
20 | full_range: TextRange, | ||
21 | focus_range: Option<TextRange>, | ||
22 | } | ||
23 | |||
24 | impl 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 @@ | |||
1 | use ra_db::{Cancelable, FilePosition}; | ||
2 | |||
3 | use 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. | ||
7 | pub(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)] | ||
20 | mod 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; | |||
4 | use test_utils::{assert_eq_dbg, assert_eq_text}; | 4 | use test_utils::{assert_eq_dbg, assert_eq_text}; |
5 | 5 | ||
6 | use ra_ide_api::{ | 6 | use 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] |
38 | fn 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] | ||
55 | fn 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] | ||
74 | fn test_resolve_crate_root() { | 38 | fn 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 | } |
diff --git a/crates/ra_lsp_server/src/conv.rs b/crates/ra_lsp_server/src/conv.rs index 35c679a4a..76fa98cbe 100644 --- a/crates/ra_lsp_server/src/conv.rs +++ b/crates/ra_lsp_server/src/conv.rs | |||
@@ -1,12 +1,12 @@ | |||
1 | use languageserver_types::{ | 1 | use languageserver_types::{ |
2 | self, CreateFile, DocumentChangeOperation, DocumentChanges, InsertTextFormat, Location, | 2 | self, CreateFile, DocumentChangeOperation, DocumentChanges, InsertTextFormat, Location, LocationLink, |
3 | Position, Range, RenameFile, ResourceOp, SymbolKind, TextDocumentEdit, TextDocumentIdentifier, | 3 | Position, Range, RenameFile, ResourceOp, SymbolKind, TextDocumentEdit, TextDocumentIdentifier, |
4 | TextDocumentItem, TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, | 4 | TextDocumentItem, TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, |
5 | WorkspaceEdit, | 5 | WorkspaceEdit, |
6 | }; | 6 | }; |
7 | use ra_ide_api::{ | 7 | use ra_ide_api::{ |
8 | CompletionItem, CompletionItemKind, FileId, FilePosition, FileRange, FileSystemEdit, | 8 | CompletionItem, CompletionItemKind, FileId, FilePosition, FileRange, FileSystemEdit, |
9 | InsertText, NavigationTarget, SourceChange, SourceFileEdit, | 9 | InsertText, NavigationTarget, SourceChange, SourceFileEdit, RangeInfo, |
10 | LineCol, LineIndex, translate_offset_with_edit | 10 | LineCol, LineIndex, translate_offset_with_edit |
11 | }; | 11 | }; |
12 | use ra_syntax::{SyntaxKind, TextRange, TextUnit}; | 12 | use ra_syntax::{SyntaxKind, TextRange, TextUnit}; |
@@ -345,10 +345,32 @@ impl TryConvWith for &NavigationTarget { | |||
345 | type Output = Location; | 345 | type Output = Location; |
346 | fn try_conv_with(self, world: &ServerWorld) -> Result<Location> { | 346 | fn try_conv_with(self, world: &ServerWorld) -> Result<Location> { |
347 | let line_index = world.analysis().file_line_index(self.file_id()); | 347 | let line_index = world.analysis().file_line_index(self.file_id()); |
348 | to_location(self.file_id(), self.range(), &world, &line_index) | 348 | let range = self.focus_range().unwrap_or(self.full_range()); |
349 | to_location(self.file_id(), range, &world, &line_index) | ||
349 | } | 350 | } |
350 | } | 351 | } |
351 | 352 | ||
353 | pub fn to_location_link( | ||
354 | target: &RangeInfo<NavigationTarget>, | ||
355 | world: &ServerWorld, | ||
356 | // line index for original range file | ||
357 | line_index: &LineIndex, | ||
358 | ) -> Result<LocationLink> { | ||
359 | let url = target.info.file_id().try_conv_with(world)?; | ||
360 | let tgt_line_index = world.analysis().file_line_index(target.info.file_id()); | ||
361 | |||
362 | let res = LocationLink { | ||
363 | origin_selection_range: Some(target.range.conv_with(line_index)), | ||
364 | target_uri: url.to_string(), | ||
365 | target_range: target.info.full_range().conv_with(&tgt_line_index), | ||
366 | target_selection_range: target | ||
367 | .info | ||
368 | .focus_range() | ||
369 | .map(|it| it.conv_with(&tgt_line_index)), | ||
370 | }; | ||
371 | Ok(res) | ||
372 | } | ||
373 | |||
352 | pub fn to_location( | 374 | pub fn to_location( |
353 | file_id: FileId, | 375 | file_id: FileId, |
354 | range: TextRange, | 376 | range: TextRange, |
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index 5f4b27149..aad9d6568 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs | |||
@@ -9,7 +9,7 @@ use languageserver_types::{ | |||
9 | SignatureInformation, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit, | 9 | SignatureInformation, SymbolInformation, TextDocumentIdentifier, TextEdit, WorkspaceEdit, |
10 | }; | 10 | }; |
11 | use ra_ide_api::{ | 11 | use ra_ide_api::{ |
12 | FileId, FilePosition, FileRange, FoldKind, Query, RunnableKind, Severity, | 12 | FileId, FilePosition, FileRange, FoldKind, Query, RunnableKind, Severity, RangeInfo, |
13 | }; | 13 | }; |
14 | use ra_syntax::{TextUnit, AstNode}; | 14 | use ra_syntax::{TextUnit, AstNode}; |
15 | use rustc_hash::FxHashMap; | 15 | use rustc_hash::FxHashMap; |
@@ -17,7 +17,7 @@ use serde_json::to_value; | |||
17 | use std::io::Write; | 17 | use std::io::Write; |
18 | 18 | ||
19 | use crate::{ | 19 | use crate::{ |
20 | conv::{to_location, Conv, ConvWith, MapConvWith, TryConvWith}, | 20 | conv::{to_location, to_location_link, Conv, ConvWith, MapConvWith, TryConvWith}, |
21 | project_model::TargetKind, | 21 | project_model::TargetKind, |
22 | req::{self, Decoration}, | 22 | req::{self, Decoration}, |
23 | server_world::ServerWorld, | 23 | server_world::ServerWorld, |
@@ -208,15 +208,19 @@ pub fn handle_goto_definition( | |||
208 | params: req::TextDocumentPositionParams, | 208 | params: req::TextDocumentPositionParams, |
209 | ) -> Result<Option<req::GotoDefinitionResponse>> { | 209 | ) -> Result<Option<req::GotoDefinitionResponse>> { |
210 | let position = params.try_conv_with(&world)?; | 210 | let position = params.try_conv_with(&world)?; |
211 | let navs = match world.analysis().goto_definition(position)? { | 211 | let line_index = world.analysis().file_line_index(position.file_id); |
212 | let nav_info = match world.analysis().goto_definition(position)? { | ||
212 | None => return Ok(None), | 213 | None => return Ok(None), |
213 | Some(it) => it, | 214 | Some(it) => it, |
214 | }; | 215 | }; |
215 | let res = navs | 216 | let nav_range = nav_info.range; |
217 | let res = nav_info | ||
218 | .info | ||
216 | .into_iter() | 219 | .into_iter() |
217 | .map(|nav| nav.try_conv_with(&world)) | 220 | .map(|nav| RangeInfo::new(nav_range, nav)) |
221 | .map(|nav| to_location_link(&nav, &world, &line_index)) | ||
218 | .collect::<Result<Vec<_>>>()?; | 222 | .collect::<Result<Vec<_>>>()?; |
219 | Ok(Some(req::GotoDefinitionResponse::Array(res))) | 223 | Ok(Some(req::GotoDefinitionResponse::Link(res))) |
220 | } | 224 | } |
221 | 225 | ||
222 | pub fn handle_parent_module( | 226 | pub fn handle_parent_module( |