aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/ra_analysis/src/completion.rs16
-rw-r--r--crates/ra_analysis/src/imp.rs211
-rw-r--r--crates/ra_analysis/src/lib.rs43
-rw-r--r--crates/ra_analysis/src/symbol_index.rs122
-rw-r--r--crates/ra_analysis/tests/tests.rs10
-rw-r--r--crates/ra_db/src/syntax_ptr.rs4
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs2
7 files changed, 236 insertions, 172 deletions
diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs
index 739ca6ae8..ce777a771 100644
--- a/crates/ra_analysis/src/completion.rs
+++ b/crates/ra_analysis/src/completion.rs
@@ -28,7 +28,21 @@ pub use crate::completion::completion_item::{CompletionItem, InsertText, Complet
28/// incomplete and can look really weird. 28/// incomplete and can look really weird.
29/// 29///
30/// Once the context is collected, we run a series of completion routines which 30/// Once the context is collected, we run a series of completion routines which
31/// look at the context and produce completion items. 31/// look at the context and produce completion items. One subtelty about this
32/// phase is that completion engine should not filter by the substring which is
33/// already present, it should give all possible variants for the identifier at
34/// the caret. In other words, for
35///
36/// ```no-run
37/// fn f() {
38/// let foo = 92;
39/// let _ = bar<|>
40/// }
41/// ```
42///
43/// `foo` *should* be present among the completion variants. Filtering by
44/// identifier prefix/fuzzy match should be done higher in the stack, together
45/// with ordering of completions (currently this is done by the client).
32pub(crate) fn completions( 46pub(crate) fn completions(
33 db: &db::RootDatabase, 47 db: &db::RootDatabase,
34 position: FilePosition, 48 position: FilePosition,
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::{
8use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase}; 8use ra_db::{FilesDatabase, SourceRoot, SourceRootId, SyntaxDatabase};
9use ra_editor::{self, find_node_at_offset, LocalEdit, Severity}; 9use ra_editor::{self, find_node_at_offset, LocalEdit, Severity};
10use ra_syntax::{ 10use 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
18use crate::{ 18use 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
516impl 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.
4macro_rules! ctry { 6macro_rules! ctry {
5 ($expr:expr) => { 7 ($expr:expr) => {
6 match $expr { 8 match $expr {
@@ -41,7 +43,7 @@ pub use ra_editor::{
41pub use hir::FnSignatureInfo; 43pub use hir::FnSignatureInfo;
42 44
43pub use ra_db::{ 45pub 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)]
223pub struct NavigationTarget { 230pub 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
228impl NavigationTarget { 239impl 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.
1use std::{ 22use std::{
2 hash::{Hash, Hasher}, 23 hash::{Hash, Hasher},
3 sync::Arc, 24 sync::Arc,
@@ -5,12 +26,12 @@ use std::{
5 26
6use fst::{self, Streamer}; 27use fst::{self, Streamer};
7use ra_syntax::{ 28use 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};
13use ra_db::{SyntaxDatabase, SourceRootId, FilesDatabase}; 34use ra_db::{SyntaxDatabase, SourceRootId, FilesDatabase, LocalSyntaxPtr};
14use salsa::ParallelDatabase; 35use salsa::ParallelDatabase;
15use rayon::prelude::*; 36use 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)]
164pub(crate) struct FileSymbol { 187pub(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
170impl 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
255fn to_symbol(node: SyntaxNodeRef) -> Option<FileSymbol> { 192fn 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,