diff options
author | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-01-03 09:59:24 +0000 |
---|---|---|
committer | bors[bot] <bors[bot]@users.noreply.github.com> | 2019-01-03 09:59:24 +0000 |
commit | e92f2ffe270c6a9fca312fb8a53cd0da0dd01fde (patch) | |
tree | 7f72fee435e82c750b321a52c551010d1b7afd09 | |
parent | f673529a3355cdd00062c873cd4fdd100f60a243 (diff) | |
parent | 8d6185350953391d0dd405cf790a69bb65d88a36 (diff) |
Merge #410
410: Detangle symbols r=matklad a=matklad
Previously, we used `FileSymbol` both to represent bytes which are stored in the index and as an API of `ra_analysis`. Mixing internal storage format and an API is not a really bright idea, so we introduce `NavigationTarget` to handle API part.
Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r-- | crates/ra_analysis/src/imp.rs | 211 | ||||
-rw-r--r-- | crates/ra_analysis/src/lib.rs | 43 | ||||
-rw-r--r-- | crates/ra_analysis/src/symbol_index.rs | 122 | ||||
-rw-r--r-- | crates/ra_analysis/tests/tests.rs | 10 | ||||
-rw-r--r-- | crates/ra_db/src/syntax_ptr.rs | 4 | ||||
-rw-r--r-- | crates/ra_lsp_server/src/main_loop/handlers.rs | 2 |
6 files changed, 221 insertions, 171 deletions
diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs index ff13247de..b812c3441 100644 --- a/crates/ra_analysis/src/imp.rs +++ b/crates/ra_analysis/src/imp.rs | |||
@@ -8,11 +8,11 @@ use hir::{ | |||
8 | use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase}; | 8 | use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase}; |
9 | use ra_editor::{self, find_node_at_offset, LocalEdit, Severity}; | 9 | use ra_editor::{self, find_node_at_offset, LocalEdit, Severity}; |
10 | use ra_syntax::{ | 10 | use ra_syntax::{ |
11 | algo::find_covering_node, | 11 | algo::{find_covering_node, visit::{visitor, Visitor}}, |
12 | ast::{self, ArgListOwner, Expr, FnDef, NameOwner}, | 12 | ast::{self, ArgListOwner, Expr, FnDef, NameOwner}, |
13 | AstNode, SourceFileNode, | 13 | AstNode, SourceFileNode, |
14 | SyntaxKind::*, | 14 | SyntaxKind::*, |
15 | SyntaxNodeRef, TextRange, TextUnit, | 15 | SyntaxNode, SyntaxNodeRef, TextRange, TextUnit, |
16 | }; | 16 | }; |
17 | 17 | ||
18 | use crate::{ | 18 | use crate::{ |
@@ -116,12 +116,13 @@ impl db::RootDatabase { | |||
116 | }; | 116 | }; |
117 | let decl = decl.borrowed(); | 117 | let decl = decl.borrowed(); |
118 | let decl_name = decl.name().unwrap(); | 118 | let decl_name = decl.name().unwrap(); |
119 | let symbol = FileSymbol { | 119 | Ok(vec![NavigationTarget { |
120 | file_id, | ||
120 | name: decl_name.text(), | 121 | name: decl_name.text(), |
121 | node_range: decl_name.syntax().range(), | 122 | range: decl_name.syntax().range(), |
122 | kind: MODULE, | 123 | kind: MODULE, |
123 | }; | 124 | ptr: None, |
124 | Ok(vec![NavigationTarget { file_id, symbol }]) | 125 | }]) |
125 | } | 126 | } |
126 | /// Returns `Vec` for the same reason as `parent_module` | 127 | /// Returns `Vec` for the same reason as `parent_module` |
127 | pub(crate) fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> { | 128 | pub(crate) fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> { |
@@ -153,14 +154,13 @@ impl db::RootDatabase { | |||
153 | let scope = fn_descr.scopes(self); | 154 | let scope = fn_descr.scopes(self); |
154 | // First try to resolve the symbol locally | 155 | // First try to resolve the symbol locally |
155 | if let Some(entry) = scope.resolve_local_name(name_ref) { | 156 | if let Some(entry) = scope.resolve_local_name(name_ref) { |
156 | rr.add_resolution( | 157 | rr.resolves_to.push(NavigationTarget { |
157 | position.file_id, | 158 | file_id: position.file_id, |
158 | FileSymbol { | 159 | name: entry.name().to_string().into(), |
159 | name: entry.name().to_string().into(), | 160 | range: entry.ptr().range(), |
160 | node_range: entry.ptr().range(), | 161 | kind: NAME, |
161 | kind: NAME, | 162 | ptr: None, |
162 | }, | 163 | }); |
163 | ); | ||
164 | return Ok(Some(rr)); | 164 | return Ok(Some(rr)); |
165 | }; | 165 | }; |
166 | } | 166 | } |
@@ -182,12 +182,14 @@ impl db::RootDatabase { | |||
182 | Some(name) => name.to_string().into(), | 182 | Some(name) => name.to_string().into(), |
183 | None => "".into(), | 183 | None => "".into(), |
184 | }; | 184 | }; |
185 | let symbol = FileSymbol { | 185 | let symbol = NavigationTarget { |
186 | file_id, | ||
186 | name, | 187 | name, |
187 | node_range: TextRange::offset_len(0.into(), 0.into()), | 188 | range: TextRange::offset_len(0.into(), 0.into()), |
188 | kind: MODULE, | 189 | kind: MODULE, |
190 | ptr: None, | ||
189 | }; | 191 | }; |
190 | rr.add_resolution(file_id, symbol); | 192 | rr.resolves_to.push(symbol); |
191 | return Ok(Some(rr)); | 193 | return Ok(Some(rr)); |
192 | } | 194 | } |
193 | } | 195 | } |
@@ -253,8 +255,7 @@ impl db::RootDatabase { | |||
253 | } | 255 | } |
254 | } | 256 | } |
255 | pub(crate) fn doc_text_for(&self, nav: NavigationTarget) -> Cancelable<Option<String>> { | 257 | pub(crate) fn doc_text_for(&self, nav: NavigationTarget) -> Cancelable<Option<String>> { |
256 | let file = self.source_file(nav.file_id); | 258 | let result = match (nav.description(self), nav.docs(self)) { |
257 | let result = match (nav.symbol.description(&file), nav.symbol.docs(&file)) { | ||
258 | (Some(desc), Some(docs)) => { | 259 | (Some(desc), Some(docs)) => { |
259 | Some("```rust\n".to_string() + &*desc + "\n```\n\n" + &*docs) | 260 | Some("```rust\n".to_string() + &*desc + "\n```\n\n" + &*docs) |
260 | } | 261 | } |
@@ -362,52 +363,52 @@ impl db::RootDatabase { | |||
362 | // Resolve the function's NameRef (NOTE: this isn't entirely accurate). | 363 | // Resolve the function's NameRef (NOTE: this isn't entirely accurate). |
363 | let file_symbols = self.index_resolve(name_ref)?; | 364 | let file_symbols = self.index_resolve(name_ref)?; |
364 | for (fn_file_id, fs) in file_symbols { | 365 | for (fn_file_id, fs) in file_symbols { |
365 | if fs.kind == FN_DEF { | 366 | if fs.ptr.kind() == FN_DEF { |
366 | let fn_file = self.source_file(fn_file_id); | 367 | let fn_file = self.source_file(fn_file_id); |
367 | if let Some(fn_def) = find_node_at_offset(fn_file.syntax(), fs.node_range.start()) { | 368 | let fn_def = fs.ptr.resolve(&fn_file); |
368 | let descr = ctry!(source_binder::function_from_source( | 369 | let fn_def = ast::FnDef::cast(fn_def.borrowed()).unwrap(); |
369 | self, fn_file_id, fn_def | 370 | let descr = ctry!(source_binder::function_from_source( |
370 | )?); | 371 | self, fn_file_id, fn_def |
371 | if let Some(descriptor) = descr.signature_info(self) { | 372 | )?); |
372 | // If we have a calling expression let's find which argument we are on | 373 | if let Some(descriptor) = descr.signature_info(self) { |
373 | let mut current_parameter = None; | 374 | // If we have a calling expression let's find which argument we are on |
374 | 375 | let mut current_parameter = None; | |
375 | let num_params = descriptor.params.len(); | 376 | |
376 | let has_self = fn_def.param_list().and_then(|l| l.self_param()).is_some(); | 377 | let num_params = descriptor.params.len(); |
377 | 378 | let has_self = fn_def.param_list().and_then(|l| l.self_param()).is_some(); | |
378 | if num_params == 1 { | 379 | |
379 | if !has_self { | 380 | if num_params == 1 { |
380 | current_parameter = Some(0); | 381 | if !has_self { |
381 | } | 382 | current_parameter = Some(0); |
382 | } else if num_params > 1 { | ||
383 | // Count how many parameters into the call we are. | ||
384 | // TODO: This is best effort for now and should be fixed at some point. | ||
385 | // It may be better to see where we are in the arg_list and then check | ||
386 | // where offset is in that list (or beyond). | ||
387 | // Revisit this after we get documentation comments in. | ||
388 | if let Some(ref arg_list) = calling_node.arg_list() { | ||
389 | let start = arg_list.syntax().range().start(); | ||
390 | |||
391 | let range_search = TextRange::from_to(start, position.offset); | ||
392 | let mut commas: usize = arg_list | ||
393 | .syntax() | ||
394 | .text() | ||
395 | .slice(range_search) | ||
396 | .to_string() | ||
397 | .matches(',') | ||
398 | .count(); | ||
399 | |||
400 | // If we have a method call eat the first param since it's just self. | ||
401 | if has_self { | ||
402 | commas += 1; | ||
403 | } | ||
404 | |||
405 | current_parameter = Some(commas); | ||
406 | } | ||
407 | } | 383 | } |
384 | } else if num_params > 1 { | ||
385 | // Count how many parameters into the call we are. | ||
386 | // TODO: This is best effort for now and should be fixed at some point. | ||
387 | // It may be better to see where we are in the arg_list and then check | ||
388 | // where offset is in that list (or beyond). | ||
389 | // Revisit this after we get documentation comments in. | ||
390 | if let Some(ref arg_list) = calling_node.arg_list() { | ||
391 | let start = arg_list.syntax().range().start(); | ||
392 | |||
393 | let range_search = TextRange::from_to(start, position.offset); | ||
394 | let mut commas: usize = arg_list | ||
395 | .syntax() | ||
396 | .text() | ||
397 | .slice(range_search) | ||
398 | .to_string() | ||
399 | .matches(',') | ||
400 | .count(); | ||
401 | |||
402 | // If we have a method call eat the first param since it's just self. | ||
403 | if has_self { | ||
404 | commas += 1; | ||
405 | } | ||
408 | 406 | ||
409 | return Ok(Some((descriptor, current_parameter))); | 407 | current_parameter = Some(commas); |
408 | } | ||
410 | } | 409 | } |
410 | |||
411 | return Ok(Some((descriptor, current_parameter))); | ||
411 | } | 412 | } |
412 | } | 413 | } |
413 | } | 414 | } |
@@ -511,3 +512,91 @@ impl<'a> FnCallNode<'a> { | |||
511 | } | 512 | } |
512 | } | 513 | } |
513 | } | 514 | } |
515 | |||
516 | impl NavigationTarget { | ||
517 | fn node(&self, db: &db::RootDatabase) -> Option<SyntaxNode> { | ||
518 | let source_file = db.source_file(self.file_id); | ||
519 | let source_file = source_file.syntax(); | ||
520 | let node = source_file | ||
521 | .descendants() | ||
522 | .find(|node| node.kind() == self.kind && node.range() == self.range)? | ||
523 | .owned(); | ||
524 | Some(node) | ||
525 | } | ||
526 | |||
527 | fn docs(&self, db: &db::RootDatabase) -> Option<String> { | ||
528 | let node = self.node(db)?; | ||
529 | let node = node.borrowed(); | ||
530 | fn doc_comments<'a, N: ast::DocCommentsOwner<'a>>(node: N) -> Option<String> { | ||
531 | let comments = node.doc_comment_text(); | ||
532 | if comments.is_empty() { | ||
533 | None | ||
534 | } else { | ||
535 | Some(comments) | ||
536 | } | ||
537 | } | ||
538 | |||
539 | visitor() | ||
540 | .visit(doc_comments::<ast::FnDef>) | ||
541 | .visit(doc_comments::<ast::StructDef>) | ||
542 | .visit(doc_comments::<ast::EnumDef>) | ||
543 | .visit(doc_comments::<ast::TraitDef>) | ||
544 | .visit(doc_comments::<ast::Module>) | ||
545 | .visit(doc_comments::<ast::TypeDef>) | ||
546 | .visit(doc_comments::<ast::ConstDef>) | ||
547 | .visit(doc_comments::<ast::StaticDef>) | ||
548 | .accept(node)? | ||
549 | } | ||
550 | |||
551 | /// Get a description of this node. | ||
552 | /// | ||
553 | /// e.g. `struct Name`, `enum Name`, `fn Name` | ||
554 | fn description(&self, db: &db::RootDatabase) -> Option<String> { | ||
555 | // TODO: After type inference is done, add type information to improve the output | ||
556 | let node = self.node(db)?; | ||
557 | let node = node.borrowed(); | ||
558 | // TODO: Refactor to be have less repetition | ||
559 | visitor() | ||
560 | .visit(|node: ast::FnDef| { | ||
561 | let mut string = "fn ".to_string(); | ||
562 | node.name()?.syntax().text().push_to(&mut string); | ||
563 | Some(string) | ||
564 | }) | ||
565 | .visit(|node: ast::StructDef| { | ||
566 | let mut string = "struct ".to_string(); | ||
567 | node.name()?.syntax().text().push_to(&mut string); | ||
568 | Some(string) | ||
569 | }) | ||
570 | .visit(|node: ast::EnumDef| { | ||
571 | let mut string = "enum ".to_string(); | ||
572 | node.name()?.syntax().text().push_to(&mut string); | ||
573 | Some(string) | ||
574 | }) | ||
575 | .visit(|node: ast::TraitDef| { | ||
576 | let mut string = "trait ".to_string(); | ||
577 | node.name()?.syntax().text().push_to(&mut string); | ||
578 | Some(string) | ||
579 | }) | ||
580 | .visit(|node: ast::Module| { | ||
581 | let mut string = "mod ".to_string(); | ||
582 | node.name()?.syntax().text().push_to(&mut string); | ||
583 | Some(string) | ||
584 | }) | ||
585 | .visit(|node: ast::TypeDef| { | ||
586 | let mut string = "type ".to_string(); | ||
587 | node.name()?.syntax().text().push_to(&mut string); | ||
588 | Some(string) | ||
589 | }) | ||
590 | .visit(|node: ast::ConstDef| { | ||
591 | let mut string = "const ".to_string(); | ||
592 | node.name()?.syntax().text().push_to(&mut string); | ||
593 | Some(string) | ||
594 | }) | ||
595 | .visit(|node: ast::StaticDef| { | ||
596 | let mut string = "static ".to_string(); | ||
597 | node.name()?.syntax().text().push_to(&mut string); | ||
598 | Some(string) | ||
599 | }) | ||
600 | .accept(node)? | ||
601 | } | ||
602 | } | ||
diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs index a01febf4e..61af676b2 100644 --- a/crates/ra_analysis/src/lib.rs +++ b/crates/ra_analysis/src/lib.rs | |||
@@ -1,6 +1,8 @@ | |||
1 | //! ra_analyzer crate is the brain of Rust analyzer. It relies on the `salsa` | 1 | //! ra_analyzer crate provides "ide-centric" APIs for the rust-analyzer. What |
2 | //! crate, which provides and incremental on-demand database of facts. | 2 | //! powers this API are the `RootDatabase` struct, which defines a `salsa` |
3 | 3 | //! database, and the `ra_hir` crate, where majority of the analysis happens. | |
4 | //! However, IDE specific bits of the analysis (most notably completion) happen | ||
5 | //! in this crate. | ||
4 | macro_rules! ctry { | 6 | macro_rules! ctry { |
5 | ($expr:expr) => { | 7 | ($expr:expr) => { |
6 | match $expr { | 8 | match $expr { |
@@ -41,7 +43,7 @@ pub use ra_editor::{ | |||
41 | pub use hir::FnSignatureInfo; | 43 | pub use hir::FnSignatureInfo; |
42 | 44 | ||
43 | pub use ra_db::{ | 45 | pub use ra_db::{ |
44 | Canceled, Cancelable, FilePosition, FileRange, | 46 | Canceled, Cancelable, FilePosition, FileRange, LocalSyntaxPtr, |
45 | CrateGraph, CrateId, SourceRootId, FileId, SyntaxDatabase, FilesDatabase | 47 | CrateGraph, CrateId, SourceRootId, FileId, SyntaxDatabase, FilesDatabase |
46 | }; | 48 | }; |
47 | 49 | ||
@@ -219,24 +221,42 @@ impl Query { | |||
219 | } | 221 | } |
220 | } | 222 | } |
221 | 223 | ||
224 | /// `NavigationTarget` represents and element in the editor's UI whihc you can | ||
225 | /// click on to navigate to a particular piece of code. | ||
226 | /// | ||
227 | /// Typically, a `NavigationTarget` corresponds to some element in the source | ||
228 | /// code, like a function or a struct, but this is not strictly required. | ||
222 | #[derive(Debug)] | 229 | #[derive(Debug)] |
223 | pub struct NavigationTarget { | 230 | pub struct NavigationTarget { |
224 | file_id: FileId, | 231 | file_id: FileId, |
225 | symbol: FileSymbol, | 232 | name: SmolStr, |
233 | kind: SyntaxKind, | ||
234 | range: TextRange, | ||
235 | // Should be DefId ideally | ||
236 | ptr: Option<LocalSyntaxPtr>, | ||
226 | } | 237 | } |
227 | 238 | ||
228 | impl NavigationTarget { | 239 | impl NavigationTarget { |
229 | pub fn name(&self) -> SmolStr { | 240 | fn from_symbol(file_id: FileId, symbol: FileSymbol) -> NavigationTarget { |
230 | self.symbol.name.clone() | 241 | NavigationTarget { |
242 | name: symbol.name.clone(), | ||
243 | kind: symbol.ptr.kind(), | ||
244 | file_id, | ||
245 | range: symbol.ptr.range(), | ||
246 | ptr: Some(symbol.ptr.clone()), | ||
247 | } | ||
248 | } | ||
249 | pub fn name(&self) -> &SmolStr { | ||
250 | &self.name | ||
231 | } | 251 | } |
232 | pub fn kind(&self) -> SyntaxKind { | 252 | pub fn kind(&self) -> SyntaxKind { |
233 | self.symbol.kind | 253 | self.kind |
234 | } | 254 | } |
235 | pub fn file_id(&self) -> FileId { | 255 | pub fn file_id(&self) -> FileId { |
236 | self.file_id | 256 | self.file_id |
237 | } | 257 | } |
238 | pub fn range(&self) -> TextRange { | 258 | pub fn range(&self) -> TextRange { |
239 | self.symbol.node_range | 259 | self.range |
240 | } | 260 | } |
241 | } | 261 | } |
242 | 262 | ||
@@ -260,7 +280,8 @@ impl ReferenceResolution { | |||
260 | } | 280 | } |
261 | 281 | ||
262 | fn add_resolution(&mut self, file_id: FileId, symbol: FileSymbol) { | 282 | fn add_resolution(&mut self, file_id: FileId, symbol: FileSymbol) { |
263 | self.resolves_to.push(NavigationTarget { file_id, symbol }) | 283 | self.resolves_to |
284 | .push(NavigationTarget::from_symbol(file_id, symbol)) | ||
264 | } | 285 | } |
265 | } | 286 | } |
266 | 287 | ||
@@ -359,7 +380,7 @@ impl Analysis { | |||
359 | pub fn symbol_search(&self, query: Query) -> Cancelable<Vec<NavigationTarget>> { | 380 | pub fn symbol_search(&self, query: Query) -> Cancelable<Vec<NavigationTarget>> { |
360 | let res = symbol_index::world_symbols(&*self.db, query)? | 381 | let res = symbol_index::world_symbols(&*self.db, query)? |
361 | .into_iter() | 382 | .into_iter() |
362 | .map(|(file_id, symbol)| NavigationTarget { file_id, symbol }) | 383 | .map(|(file_id, symbol)| NavigationTarget::from_symbol(file_id, symbol)) |
363 | .collect(); | 384 | .collect(); |
364 | Ok(res) | 385 | Ok(res) |
365 | } | 386 | } |
diff --git a/crates/ra_analysis/src/symbol_index.rs b/crates/ra_analysis/src/symbol_index.rs index ddcf3d052..10d8e8059 100644 --- a/crates/ra_analysis/src/symbol_index.rs +++ b/crates/ra_analysis/src/symbol_index.rs | |||
@@ -1,3 +1,24 @@ | |||
1 | //! This module handles fuzzy-searching of functions, structs and other symbols | ||
2 | //! by name across the whole workspace and dependencies. | ||
3 | //! | ||
4 | //! It works by building an incrementally-updated text-search index of all | ||
5 | //! symbols. The backbone of the index is the **awesome** `fst` crate by | ||
6 | //! @BurntSushi. | ||
7 | //! | ||
8 | //! In a nutshell, you give a set of strings to the `fst`, and it builds a | ||
9 | //! finite state machine describing this set of strtings. The strings which | ||
10 | //! could fuzzy-match a pattern can also be described by a finite state machine. | ||
11 | //! What is freakingly cool is that you can now traverse both state machines in | ||
12 | //! lock-step to enumerate the strings which are both in the input set and | ||
13 | //! fuzz-match the query. Or, more formally, given two langauges described by | ||
14 | //! fsts, one can build an product fst which describes the intersection of the | ||
15 | //! languages. | ||
16 | //! | ||
17 | //! `fst` does not support cheap updating of the index, but it supports unioning | ||
18 | //! of state machines. So, to account for changing source code, we build an fst | ||
19 | //! for each library (which is assumed to never change) and an fst for each rust | ||
20 | //! file in the current workspace, and run a query aginst the union of all | ||
21 | //! thouse fsts. | ||
1 | use std::{ | 22 | use std::{ |
2 | hash::{Hash, Hasher}, | 23 | hash::{Hash, Hasher}, |
3 | sync::Arc, | 24 | sync::Arc, |
@@ -5,12 +26,12 @@ use std::{ | |||
5 | 26 | ||
6 | use fst::{self, Streamer}; | 27 | use fst::{self, Streamer}; |
7 | use ra_syntax::{ | 28 | use ra_syntax::{ |
8 | AstNode, SyntaxNodeRef, SourceFileNode, SmolStr, TextRange, | 29 | SyntaxNodeRef, SourceFileNode, SmolStr, |
9 | algo::visit::{visitor, Visitor}, | 30 | algo::visit::{visitor, Visitor}, |
10 | SyntaxKind::{self, *}, | 31 | SyntaxKind::{self, *}, |
11 | ast::{self, NameOwner, DocCommentsOwner}, | 32 | ast::{self, NameOwner}, |
12 | }; | 33 | }; |
13 | use ra_db::{SyntaxDatabase, SourceRootId, FilesDatabase}; | 34 | use ra_db::{SyntaxDatabase, SourceRootId, FilesDatabase, LocalSyntaxPtr}; |
14 | use salsa::ParallelDatabase; | 35 | use salsa::ParallelDatabase; |
15 | use rayon::prelude::*; | 36 | use rayon::prelude::*; |
16 | 37 | ||
@@ -140,7 +161,7 @@ impl Query { | |||
140 | let idx = indexed_value.value as usize; | 161 | let idx = indexed_value.value as usize; |
141 | 162 | ||
142 | let (file_id, symbol) = &file_symbols.symbols[idx]; | 163 | let (file_id, symbol) = &file_symbols.symbols[idx]; |
143 | if self.only_types && !is_type(symbol.kind) { | 164 | if self.only_types && !is_type(symbol.ptr.kind()) { |
144 | continue; | 165 | continue; |
145 | } | 166 | } |
146 | if self.exact && symbol.name != self.query { | 167 | if self.exact && symbol.name != self.query { |
@@ -160,96 +181,12 @@ fn is_type(kind: SyntaxKind) -> bool { | |||
160 | } | 181 | } |
161 | } | 182 | } |
162 | 183 | ||
184 | /// The actual data that is stored in the index. It should be as compact as | ||
185 | /// possible. | ||
163 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] | 186 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
164 | pub(crate) struct FileSymbol { | 187 | pub(crate) struct FileSymbol { |
165 | pub(crate) name: SmolStr, | 188 | pub(crate) name: SmolStr, |
166 | pub(crate) node_range: TextRange, | 189 | pub(crate) ptr: LocalSyntaxPtr, |
167 | pub(crate) kind: SyntaxKind, | ||
168 | } | ||
169 | |||
170 | impl FileSymbol { | ||
171 | pub(crate) fn docs(&self, file: &SourceFileNode) -> Option<String> { | ||
172 | file.syntax() | ||
173 | .descendants() | ||
174 | .filter(|node| node.kind() == self.kind && node.range() == self.node_range) | ||
175 | .filter_map(|node: SyntaxNodeRef| { | ||
176 | fn doc_comments<'a, N: DocCommentsOwner<'a>>(node: N) -> Option<String> { | ||
177 | let comments = node.doc_comment_text(); | ||
178 | if comments.is_empty() { | ||
179 | None | ||
180 | } else { | ||
181 | Some(comments) | ||
182 | } | ||
183 | } | ||
184 | |||
185 | visitor() | ||
186 | .visit(doc_comments::<ast::FnDef>) | ||
187 | .visit(doc_comments::<ast::StructDef>) | ||
188 | .visit(doc_comments::<ast::EnumDef>) | ||
189 | .visit(doc_comments::<ast::TraitDef>) | ||
190 | .visit(doc_comments::<ast::Module>) | ||
191 | .visit(doc_comments::<ast::TypeDef>) | ||
192 | .visit(doc_comments::<ast::ConstDef>) | ||
193 | .visit(doc_comments::<ast::StaticDef>) | ||
194 | .accept(node)? | ||
195 | }) | ||
196 | .nth(0) | ||
197 | } | ||
198 | /// Get a description of this node. | ||
199 | /// | ||
200 | /// e.g. `struct Name`, `enum Name`, `fn Name` | ||
201 | pub(crate) fn description(&self, file: &SourceFileNode) -> Option<String> { | ||
202 | // TODO: After type inference is done, add type information to improve the output | ||
203 | file.syntax() | ||
204 | .descendants() | ||
205 | .filter(|node| node.kind() == self.kind && node.range() == self.node_range) | ||
206 | .filter_map(|node: SyntaxNodeRef| { | ||
207 | // TODO: Refactor to be have less repetition | ||
208 | visitor() | ||
209 | .visit(|node: ast::FnDef| { | ||
210 | let mut string = "fn ".to_string(); | ||
211 | node.name()?.syntax().text().push_to(&mut string); | ||
212 | Some(string) | ||
213 | }) | ||
214 | .visit(|node: ast::StructDef| { | ||
215 | let mut string = "struct ".to_string(); | ||
216 | node.name()?.syntax().text().push_to(&mut string); | ||
217 | Some(string) | ||
218 | }) | ||
219 | .visit(|node: ast::EnumDef| { | ||
220 | let mut string = "enum ".to_string(); | ||
221 | node.name()?.syntax().text().push_to(&mut string); | ||
222 | Some(string) | ||
223 | }) | ||
224 | .visit(|node: ast::TraitDef| { | ||
225 | let mut string = "trait ".to_string(); | ||
226 | node.name()?.syntax().text().push_to(&mut string); | ||
227 | Some(string) | ||
228 | }) | ||
229 | .visit(|node: ast::Module| { | ||
230 | let mut string = "mod ".to_string(); | ||
231 | node.name()?.syntax().text().push_to(&mut string); | ||
232 | Some(string) | ||
233 | }) | ||
234 | .visit(|node: ast::TypeDef| { | ||
235 | let mut string = "type ".to_string(); | ||
236 | node.name()?.syntax().text().push_to(&mut string); | ||
237 | Some(string) | ||
238 | }) | ||
239 | .visit(|node: ast::ConstDef| { | ||
240 | let mut string = "const ".to_string(); | ||
241 | node.name()?.syntax().text().push_to(&mut string); | ||
242 | Some(string) | ||
243 | }) | ||
244 | .visit(|node: ast::StaticDef| { | ||
245 | let mut string = "static ".to_string(); | ||
246 | node.name()?.syntax().text().push_to(&mut string); | ||
247 | Some(string) | ||
248 | }) | ||
249 | .accept(node)? | ||
250 | }) | ||
251 | .nth(0) | ||
252 | } | ||
253 | } | 190 | } |
254 | 191 | ||
255 | fn to_symbol(node: SyntaxNodeRef) -> Option<FileSymbol> { | 192 | fn to_symbol(node: SyntaxNodeRef) -> Option<FileSymbol> { |
@@ -257,8 +194,7 @@ fn to_symbol(node: SyntaxNodeRef) -> Option<FileSymbol> { | |||
257 | let name = node.name()?; | 194 | let name = node.name()?; |
258 | Some(FileSymbol { | 195 | Some(FileSymbol { |
259 | name: name.text(), | 196 | name: name.text(), |
260 | node_range: node.syntax().range(), | 197 | ptr: LocalSyntaxPtr::new(node.syntax()), |
261 | kind: node.syntax().kind(), | ||
262 | }) | 198 | }) |
263 | } | 199 | } |
264 | visitor() | 200 | visitor() |
diff --git a/crates/ra_analysis/tests/tests.rs b/crates/ra_analysis/tests/tests.rs index 3045c2e78..bcf29d29c 100644 --- a/crates/ra_analysis/tests/tests.rs +++ b/crates/ra_analysis/tests/tests.rs | |||
@@ -25,7 +25,7 @@ fn approximate_resolve_works_in_items() { | |||
25 | assert_eq_dbg( | 25 | assert_eq_dbg( |
26 | r#"ReferenceResolution { | 26 | r#"ReferenceResolution { |
27 | reference_range: [23; 26), | 27 | reference_range: [23; 26), |
28 | resolves_to: [NavigationTarget { file_id: FileId(1), symbol: FileSymbol { name: "Foo", node_range: [0; 11), kind: STRUCT_DEF } }] | 28 | resolves_to: [NavigationTarget { file_id: FileId(1), name: "Foo", kind: STRUCT_DEF, range: [0; 11), ptr: Some(LocalSyntaxPtr { range: [0; 11), kind: STRUCT_DEF }) }] |
29 | }"#, | 29 | }"#, |
30 | &symbols, | 30 | &symbols, |
31 | ); | 31 | ); |
@@ -46,7 +46,7 @@ fn test_resolve_module() { | |||
46 | assert_eq_dbg( | 46 | assert_eq_dbg( |
47 | r#"ReferenceResolution { | 47 | r#"ReferenceResolution { |
48 | reference_range: [4; 7), | 48 | reference_range: [4; 7), |
49 | resolves_to: [NavigationTarget { file_id: FileId(2), symbol: FileSymbol { name: "foo", node_range: [0; 0), kind: MODULE } }] | 49 | resolves_to: [NavigationTarget { file_id: FileId(2), name: "foo", kind: MODULE, range: [0; 0), ptr: None }] |
50 | }"#, | 50 | }"#, |
51 | &symbols, | 51 | &symbols, |
52 | ); | 52 | ); |
@@ -64,7 +64,7 @@ fn test_resolve_module() { | |||
64 | assert_eq_dbg( | 64 | assert_eq_dbg( |
65 | r#"ReferenceResolution { | 65 | r#"ReferenceResolution { |
66 | reference_range: [4; 7), | 66 | reference_range: [4; 7), |
67 | resolves_to: [NavigationTarget { file_id: FileId(2), symbol: FileSymbol { name: "foo", node_range: [0; 0), kind: MODULE } }] | 67 | resolves_to: [NavigationTarget { file_id: FileId(2), name: "foo", kind: MODULE, range: [0; 0), ptr: None }] |
68 | }"#, | 68 | }"#, |
69 | &symbols, | 69 | &symbols, |
70 | ); | 70 | ); |
@@ -107,7 +107,7 @@ fn test_resolve_parent_module() { | |||
107 | ); | 107 | ); |
108 | let symbols = analysis.parent_module(pos).unwrap(); | 108 | let symbols = analysis.parent_module(pos).unwrap(); |
109 | assert_eq_dbg( | 109 | assert_eq_dbg( |
110 | r#"[NavigationTarget { file_id: FileId(1), symbol: FileSymbol { name: "foo", node_range: [4; 7), kind: MODULE } }]"#, | 110 | r#"[NavigationTarget { file_id: FileId(1), name: "foo", kind: MODULE, range: [4; 7), ptr: None }]"#, |
111 | &symbols, | 111 | &symbols, |
112 | ); | 112 | ); |
113 | } | 113 | } |
@@ -126,7 +126,7 @@ fn test_resolve_parent_module_for_inline() { | |||
126 | ); | 126 | ); |
127 | let symbols = analysis.parent_module(pos).unwrap(); | 127 | let symbols = analysis.parent_module(pos).unwrap(); |
128 | assert_eq_dbg( | 128 | assert_eq_dbg( |
129 | r#"[NavigationTarget { file_id: FileId(1), symbol: FileSymbol { name: "bar", node_range: [18; 21), kind: MODULE } }]"#, | 129 | r#"[NavigationTarget { file_id: FileId(1), name: "bar", kind: MODULE, range: [18; 21), ptr: None }]"#, |
130 | &symbols, | 130 | &symbols, |
131 | ); | 131 | ); |
132 | } | 132 | } |
diff --git a/crates/ra_db/src/syntax_ptr.rs b/crates/ra_db/src/syntax_ptr.rs index dac94dd36..744cb2352 100644 --- a/crates/ra_db/src/syntax_ptr.rs +++ b/crates/ra_db/src/syntax_ptr.rs | |||
@@ -31,6 +31,10 @@ impl LocalSyntaxPtr { | |||
31 | pub fn range(self) -> TextRange { | 31 | pub fn range(self) -> TextRange { |
32 | self.range | 32 | self.range |
33 | } | 33 | } |
34 | |||
35 | pub fn kind(self) -> SyntaxKind { | ||
36 | self.kind | ||
37 | } | ||
34 | } | 38 | } |
35 | 39 | ||
36 | #[test] | 40 | #[test] |
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index 26b6c7d8a..b5792f3b8 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs | |||
@@ -190,7 +190,7 @@ pub fn handle_workspace_symbol( | |||
190 | let mut res = Vec::new(); | 190 | let mut res = Vec::new(); |
191 | for nav in world.analysis().symbol_search(query)? { | 191 | for nav in world.analysis().symbol_search(query)? { |
192 | let info = SymbolInformation { | 192 | let info = SymbolInformation { |
193 | name: nav.name().into(), | 193 | name: nav.name().to_string(), |
194 | kind: nav.kind().conv(), | 194 | kind: nav.kind().conv(), |
195 | location: nav.try_conv_with(world)?, | 195 | location: nav.try_conv_with(world)?, |
196 | container_name: None, | 196 | container_name: None, |