1 //! This module handles fuzzy-searching of functions, structs and other symbols
2 //! by name across the whole workspace and dependencies.
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
8 //! In a nutshell, you give a set of strings to `fst`, and it builds a
9 //! finite state machine describing this set of strings. The strings which
10 //! could fuzzy-match a pattern can also be described by a finite state machine.
11 //! What is freaking 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 languages described by
14 //! FSTs, one can build a product FST which describes the intersection of the
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 against the union of all
32 salsa::{self, ParallelDatabase},
33 CrateId, FileId, FileRange, SourceDatabaseExt, SourceRootId, Upcast,
35 use fst::{self, Streamer};
37 db::DefDatabase, AdtId, AssocContainerId, AssocItemLoc, DefHasSource, DefWithBodyId, HirFileId,
38 InFile, ItemLoc, ItemScope, ItemTreeNode, Lookup, ModuleData, ModuleDefId, ModuleId, Semantics,
40 use rayon::prelude::*;
41 use rustc_hash::{FxHashMap, FxHashSet};
44 AstNode, Parse, SmolStr, SourceFile, SyntaxNode, SyntaxNodePtr,
47 use crate::RootDatabase;
61 pub fn new(query: String) -> Query {
62 let lowercased = query.to_lowercase();
69 case_sensitive: false,
70 limit: usize::max_value(),
74 pub fn only_types(&mut self) {
75 self.only_types = true;
78 pub fn libs(&mut self) {
82 pub fn exact(&mut self) {
86 pub fn case_sensitive(&mut self) {
87 self.case_sensitive = true;
90 pub fn limit(&mut self, limit: usize) {
95 #[salsa::query_group(SymbolsDatabaseStorage)]
96 pub trait SymbolsDatabase: hir::db::HirDatabase + SourceDatabaseExt {
97 fn module_symbols(&self, module_id: ModuleId) -> Arc<SymbolIndex>;
98 fn library_symbols(&self) -> Arc<FxHashMap<SourceRootId, SymbolIndex>>;
99 /// The set of "local" (that is, from the current workspace) roots.
100 /// Files in local roots are assumed to change frequently.
102 fn local_roots(&self) -> Arc<FxHashSet<SourceRootId>>;
103 /// The set of roots for crates.io libraries.
104 /// Files in libraries are assumed to never change.
106 fn library_roots(&self) -> Arc<FxHashSet<SourceRootId>>;
109 fn library_symbols(db: &dyn SymbolsDatabase) -> Arc<FxHashMap<SourceRootId, SymbolIndex>> {
110 let _p = profile::span("library_symbols");
112 let roots = db.library_roots();
116 let root = db.source_root(root_id);
119 .map(|it| (it, SourceDatabaseExt::file_text(db, it)))
120 .collect::<Vec<_>>();
121 let symbol_index = SymbolIndex::for_files(
122 files.into_par_iter().map(|(file, text)| (file, SourceFile::parse(&text))),
124 (root_id, symbol_index)
130 fn module_symbols(db: &dyn SymbolsDatabase, module_id: ModuleId) -> Arc<SymbolIndex> {
131 db.unwind_if_cancelled();
133 let def_map = module_id.def_map(db.upcast());
134 let module_data = &def_map[module_id.local_id];
136 let symbols = module_data_to_file_symbols(db.upcast(), module_data);
138 Arc::new(SymbolIndex::new(symbols))
141 /// Need to wrap Snapshot to provide `Clone` impl for `map_with`
143 impl<DB: ParallelDatabase> Clone for Snap<salsa::Snapshot<DB>> {
144 fn clone(&self) -> Snap<salsa::Snapshot<DB>> {
145 Snap(self.0.snapshot())
149 // Feature: Workspace Symbol
151 // Uses fuzzy-search to find types, modules and functions by name across your
152 // project and dependencies. This is **the** most useful feature, which improves code
153 // navigation tremendously. It mostly works on top of the built-in LSP
154 // functionality, however `#` and `*` symbols can be used to narrow down the
155 // search. Specifically,
157 // - `Foo` searches for `Foo` type in the current workspace
158 // - `foo#` searches for `foo` function in the current workspace
159 // - `Foo*` searches for `Foo` type among dependencies, including `stdlib`
160 // - `foo#*` searches for `foo` function among dependencies
162 // That is, `#` switches from "types" to all symbols, `*` switches from the current
163 // workspace to dependencies.
165 // Note that filtering does not currently work in VSCode due to the editor never
166 // sending the special symbols to the language server. Instead, you can configure
167 // the filtering via the `rust-analyzer.workspace.symbol.search.scope` and
168 // `rust-analyzer.workspace.symbol.search.kind` settings.
171 // | Editor | Shortcut
173 // | VS Code | kbd:[Ctrl+T]
175 pub fn world_symbols(db: &RootDatabase, query: Query) -> Vec<FileSymbol> {
176 let _p = profile::span("world_symbols").detail(|| query.query.clone());
180 let buf: Vec<&SymbolIndex> = if query.libs {
181 tmp1 = db.library_symbols();
182 tmp1.values().collect()
184 let mut module_ids = Vec::new();
186 for &root in db.local_roots().iter() {
187 let crates = db.source_root_crates(root);
188 for &krate in crates.iter() {
189 module_ids.extend(module_ids_for_crate(db, krate));
193 let snap = Snap(db.snapshot());
196 .map_with(snap, |snap, &module_id| snap.0.module_symbols(module_id))
197 .collect::<Vec<_>>();
198 tmp2.iter().map(|it| &**it).collect()
203 pub fn crate_symbols(db: &RootDatabase, krate: CrateId, query: Query) -> Vec<FileSymbol> {
204 let _p = profile::span("crate_symbols").detail(|| format!("{:?}", query));
206 let module_ids = module_ids_for_crate(db, krate);
207 let snap = Snap(db.snapshot());
208 let buf: Vec<_> = module_ids
210 .map_with(snap, |snap, &module_id| snap.0.module_symbols(module_id))
212 let buf = buf.iter().map(|it| &**it).collect::<Vec<_>>();
216 fn module_ids_for_crate(db: &RootDatabase, krate: CrateId) -> Vec<ModuleId> {
217 let def_map = db.crate_def_map(krate);
218 let mut module_ids = Vec::new();
219 let mut modules = vec![def_map.root()];
220 while let Some(module) = modules.pop() {
221 let data = &def_map[module];
222 module_ids.push(def_map.module_id(module));
223 modules.extend(data.children.values());
229 pub fn index_resolve(db: &RootDatabase, name: &str) -> Vec<FileSymbol> {
230 let mut query = Query::new(name.to_string());
233 world_symbols(db, query)
237 pub struct SymbolIndex {
238 symbols: Vec<FileSymbol>,
239 map: fst::Map<Vec<u8>>,
242 impl fmt::Debug for SymbolIndex {
243 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
244 f.debug_struct("SymbolIndex").field("n_symbols", &self.symbols.len()).finish()
248 impl PartialEq for SymbolIndex {
249 fn eq(&self, other: &SymbolIndex) -> bool {
250 self.symbols == other.symbols
254 impl Eq for SymbolIndex {}
256 impl Hash for SymbolIndex {
257 fn hash<H: Hasher>(&self, hasher: &mut H) {
258 self.symbols.hash(hasher)
263 fn new(mut symbols: Vec<FileSymbol>) -> SymbolIndex {
264 fn cmp(lhs: &FileSymbol, rhs: &FileSymbol) -> Ordering {
265 let lhs_chars = lhs.name.chars().map(|c| c.to_ascii_lowercase());
266 let rhs_chars = rhs.name.chars().map(|c| c.to_ascii_lowercase());
267 lhs_chars.cmp(rhs_chars)
270 symbols.par_sort_by(cmp);
272 let mut builder = fst::MapBuilder::memory();
274 let mut last_batch_start = 0;
276 for idx in 0..symbols.len() {
277 if let Some(next_symbol) = symbols.get(idx + 1) {
278 if cmp(&symbols[last_batch_start], next_symbol) == Ordering::Equal {
283 let start = last_batch_start;
285 last_batch_start = end;
287 let key = symbols[start].name.as_str().to_ascii_lowercase();
288 let value = SymbolIndex::range_to_map_value(start, end);
290 builder.insert(key, value).unwrap();
293 let map = fst::Map::new(builder.into_inner().unwrap()).unwrap();
294 SymbolIndex { symbols, map }
297 pub fn len(&self) -> usize {
301 pub fn memory_size(&self) -> usize {
302 self.map.as_fst().size() + self.symbols.len() * mem::size_of::<FileSymbol>()
305 pub(crate) fn for_files(
306 files: impl ParallelIterator<Item = (FileId, Parse<ast::SourceFile>)>,
309 .flat_map(|(file_id, file)| source_file_to_file_symbols(&file.tree(), file_id))
310 .collect::<Vec<_>>();
311 SymbolIndex::new(symbols)
314 fn range_to_map_value(start: usize, end: usize) -> u64 {
315 debug_assert![start <= (std::u32::MAX as usize)];
316 debug_assert![end <= (std::u32::MAX as usize)];
318 ((start as u64) << 32) | end as u64
321 fn map_value_to_range(value: u64) -> (usize, usize) {
322 let end = value as u32 as usize;
323 let start = (value >> 32) as usize;
329 pub(crate) fn search(self, indices: &[&SymbolIndex]) -> Vec<FileSymbol> {
330 let _p = profile::span("symbol_index::Query::search");
331 let mut op = fst::map::OpBuilder::new();
332 for file_symbols in indices.iter() {
333 let automaton = fst::automaton::Subsequence::new(&self.lowercased);
334 op = op.add(file_symbols.map.search(automaton))
336 let mut stream = op.union();
337 let mut res = Vec::new();
338 while let Some((_, indexed_values)) = stream.next() {
339 for indexed_value in indexed_values {
340 let symbol_index = &indices[indexed_value.index];
341 let (start, end) = SymbolIndex::map_value_to_range(indexed_value.value);
343 for symbol in &symbol_index.symbols[start..end] {
344 if self.only_types && !symbol.kind.is_type() {
348 if symbol.name != self.query {
351 } else if self.case_sensitive {
352 if self.query.chars().any(|c| !symbol.name.contains(c)) {
357 res.push(symbol.clone());
358 if res.len() >= self.limit {
368 /// The actual data that is stored in the index. It should be as compact as
370 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
371 pub struct FileSymbol {
373 pub loc: DeclarationLocation,
374 pub kind: FileSymbolKind,
375 pub container_name: Option<SmolStr>,
378 #[derive(Debug, Clone, PartialEq, Eq, Hash)]
379 pub struct DeclarationLocation {
380 /// The file id for both the `ptr` and `name_ptr`.
381 pub hir_file_id: HirFileId,
382 /// This points to the whole syntax node of the declaration.
383 pub ptr: SyntaxNodePtr,
384 /// This points to the [`syntax::ast::Name`] identifier of the declaration.
385 pub name_ptr: SyntaxNodePtr,
388 impl DeclarationLocation {
389 pub fn syntax(&self, semantics: &Semantics<'_, RootDatabase>) -> Option<SyntaxNode> {
390 let root = semantics.parse_or_expand(self.hir_file_id)?;
391 Some(self.ptr.to_node(&root))
394 pub fn original_range(&self, semantics: &Semantics<'_, RootDatabase>) -> Option<FileRange> {
395 find_original_file_range(semantics, self.hir_file_id, &self.ptr)
398 pub fn original_name_range(
400 semantics: &Semantics<'_, RootDatabase>,
401 ) -> Option<FileRange> {
402 find_original_file_range(semantics, self.hir_file_id, &self.name_ptr)
406 fn find_original_file_range(
407 semantics: &Semantics<'_, RootDatabase>,
410 ) -> Option<FileRange> {
411 let root = semantics.parse_or_expand(file_id)?;
412 let node = ptr.to_node(&root);
413 let node = InFile::new(file_id, &node);
415 Some(node.original_file_range(semantics.db.upcast()))
418 #[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
419 pub enum FileSymbolKind {
432 impl FileSymbolKind {
433 fn is_type(self: FileSymbolKind) -> bool {
436 FileSymbolKind::Struct
437 | FileSymbolKind::Enum
438 | FileSymbolKind::Trait
439 | FileSymbolKind::TypeAlias
440 | FileSymbolKind::Union
445 fn source_file_to_file_symbols(_source_file: &SourceFile, _file_id: FileId) -> Vec<FileSymbol> {
446 // todo: delete this.
450 fn module_data_to_file_symbols(db: &dyn DefDatabase, module_data: &ModuleData) -> Vec<FileSymbol> {
451 let mut symbols = Vec::new();
452 collect_symbols_from_item_scope(db, &mut symbols, &module_data.scope);
453 // todo: collect macros from scope.macros().
457 fn collect_symbols_from_item_scope(
458 db: &dyn DefDatabase,
459 symbols: &mut Vec<FileSymbol>,
462 fn container_name(db: &dyn DefDatabase, container: AssocContainerId) -> Option<SmolStr> {
464 AssocContainerId::ModuleId(module_id) => {
465 let def_map = module_id.def_map(db);
466 let module_data = &def_map[module_id.local_id];
470 .and_then(|s| s.to_node(db.upcast()).name().map(|n| n.text().into()))
472 AssocContainerId::TraitId(trait_id) => {
473 let loc = trait_id.lookup(db);
474 let source = loc.source(db);
475 source.value.name().map(|n| n.text().into())
477 AssocContainerId::ImplId(_) => None,
481 fn decl_assoc<L, T>(db: &dyn DefDatabase, id: L, kind: FileSymbolKind) -> Option<FileSymbol>
483 L: Lookup<Data = AssocItemLoc<T>>,
485 <T as ItemTreeNode>::Source: HasName,
487 let loc = id.lookup(db);
488 let source = loc.source(db);
489 let name_node = source.value.name()?;
490 let container_name = container_name(db, loc.container);
493 name: name_node.text().into(),
496 loc: DeclarationLocation {
497 hir_file_id: source.file_id,
498 ptr: SyntaxNodePtr::new(source.value.syntax()),
499 name_ptr: SyntaxNodePtr::new(name_node.syntax()),
504 fn decl<L, T>(db: &dyn DefDatabase, id: L, kind: FileSymbolKind) -> Option<FileSymbol>
506 L: Lookup<Data = ItemLoc<T>>,
508 <T as ItemTreeNode>::Source: HasName,
510 let loc = id.lookup(db);
511 let source = loc.source(db);
512 let name_node = source.value.name()?;
515 name: name_node.text().into(),
517 container_name: None,
518 loc: DeclarationLocation {
519 hir_file_id: source.file_id,
520 ptr: SyntaxNodePtr::new(source.value.syntax()),
521 name_ptr: SyntaxNodePtr::new(name_node.syntax()),
526 fn decl_module(db: &dyn DefDatabase, module_id: ModuleId) -> Option<FileSymbol> {
527 let def_map = module_id.def_map(db);
528 let module_data = &def_map[module_id.local_id];
529 let declaration = module_data.origin.declaration()?;
530 let module = declaration.to_node(db.upcast());
531 let name_node = module.name()?;
534 name: name_node.text().into(),
535 kind: FileSymbolKind::Module,
536 container_name: None,
537 loc: DeclarationLocation {
538 hir_file_id: declaration.file_id,
539 ptr: SyntaxNodePtr::new(module.syntax()),
540 name_ptr: SyntaxNodePtr::new(name_node.syntax()),
545 let collect_symbols_from_scope =
547 symbols: &mut Vec<FileSymbol>,
548 bodies_to_traverse: &mut Vec<DefWithBodyId>| {
550 scope.declarations().filter_map(|module_def_id| match module_def_id {
551 ModuleDefId::ModuleId(module_id) => decl_module(db, module_id),
552 ModuleDefId::FunctionId(function_id) => {
553 bodies_to_traverse.push(function_id.into());
554 decl_assoc(db, function_id, FileSymbolKind::Function)
556 ModuleDefId::AdtId(AdtId::StructId(struct_id)) => {
557 decl(db, struct_id, FileSymbolKind::Struct)
559 ModuleDefId::AdtId(AdtId::EnumId(enum_id)) => {
560 decl(db, enum_id, FileSymbolKind::Enum)
562 ModuleDefId::AdtId(AdtId::UnionId(union_id)) => {
563 decl(db, union_id, FileSymbolKind::Union)
565 ModuleDefId::ConstId(const_id) => {
566 bodies_to_traverse.push(const_id.into());
567 decl_assoc(db, const_id, FileSymbolKind::Const)
569 ModuleDefId::StaticId(static_id) => {
570 bodies_to_traverse.push(static_id.into());
571 decl(db, static_id, FileSymbolKind::Static)
573 ModuleDefId::TraitId(trait_id) => decl(db, trait_id, FileSymbolKind::Trait),
574 ModuleDefId::TypeAliasId(alias_id) => {
575 decl_assoc(db, alias_id, FileSymbolKind::TypeAlias)
577 ModuleDefId::BuiltinType(_) => None,
578 ModuleDefId::EnumVariantId(_) => None,
581 symbols.extend(symbols_iter);
583 for const_id in scope.unnamed_consts() {
584 bodies_to_traverse.push(const_id.into())
588 let mut bodies_to_traverse = Vec::new();
589 collect_symbols_from_scope(scope, symbols, &mut bodies_to_traverse);
591 while let Some(body) = bodies_to_traverse.pop() {
592 let body = db.body(body);
594 for (_, block_def_map) in body.blocks(db) {
595 for (_, module_data) in block_def_map.modules() {
596 collect_symbols_from_scope(&module_data.scope, symbols, &mut bodies_to_traverse);