diff options
Diffstat (limited to 'crates/ra_analysis/src/symbol_index.rs')
-rw-r--r-- | crates/ra_analysis/src/symbol_index.rs | 122 |
1 files changed, 29 insertions, 93 deletions
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() |