From d60638e5e6d768e7fb5ccda5d64d713c2253f1b7 Mon Sep 17 00:00:00 2001 From: Lukas Wirth Date: Thu, 4 Jan 2024 22:53:41 +0100 Subject: [PATCH] Deduplicate some code --- crates/hir-def/src/import_map.rs | 150 ++++++++++++++++-------------- crates/hir/src/symbols.rs | 4 +- crates/ide-db/src/symbol_index.rs | 51 +--------- 3 files changed, 85 insertions(+), 120 deletions(-) diff --git a/crates/hir-def/src/import_map.rs b/crates/hir-def/src/import_map.rs index 9a40d30637ab..989bbc7bfb2d 100644 --- a/crates/hir-def/src/import_map.rs +++ b/crates/hir-def/src/import_map.rs @@ -83,29 +83,42 @@ impl ImportMap { .iter() // We've only collected items, whose name cannot be tuple field so unwrapping is fine. .flat_map(|(&item, (info, _))| { - info.iter().enumerate().map(move |(idx, info)| { - (item, info.name.as_str().unwrap().to_ascii_lowercase(), idx as u32) - }) + info.iter() + .enumerate() + .map(move |(idx, info)| (item, info.name.to_smol_str(), idx as u32)) }) .collect(); - importables.sort_by(|(_, lhs_name, _), (_, rhs_name, _)| lhs_name.cmp(rhs_name)); + importables.sort_by(|(_, l_info, _), (_, r_info, _)| { + let lhs_chars = l_info.chars().map(|c| c.to_ascii_lowercase()); + let rhs_chars = r_info.chars().map(|c| c.to_ascii_lowercase()); + lhs_chars.cmp(rhs_chars) + }); importables.dedup(); // Build the FST, taking care not to insert duplicate values. let mut builder = fst::MapBuilder::memory(); - let iter = importables + let mut iter = importables .iter() .enumerate() - .dedup_by(|(_, (_, lhs, _)), (_, (_, rhs, _))| lhs == rhs); - for (start_idx, (_, name, _)) in iter { - let _ = builder.insert(name, start_idx as u64); + .dedup_by(|&(_, (_, lhs, _)), &(_, (_, rhs, _))| lhs.eq_ignore_ascii_case(rhs)); + + let mut insert = |name: &str, start, end| { + builder.insert(name.to_ascii_lowercase(), ((start as u64) << 32) | end as u64).unwrap() + }; + + if let Some((mut last, (_, name, _))) = iter.next() { + debug_assert_eq!(last, 0); + let mut last_name = name; + for (next, (_, next_name, _)) in iter { + insert(last_name, last, next); + last = next; + last_name = next_name; + } + insert(last_name, last, importables.len()); } - Arc::new(ImportMap { - item_to_info_map: map, - fst: builder.into_map(), - importables: importables.into_iter().map(|(item, _, idx)| (item, idx)).collect(), - }) + let importables = importables.into_iter().map(|(item, _, idx)| (item, idx)).collect(); + Arc::new(ImportMap { item_to_info_map: map, fst: builder.into_map(), importables }) } pub fn import_info_for(&self, item: ItemInNs) -> Option<&[ImportInfo]> { @@ -266,8 +279,8 @@ impl fmt::Debug for ImportMap { } /// A way to match import map contents against the search query. -#[derive(Copy, Clone, Debug)] -enum SearchMode { +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum SearchMode { /// Import map entry should strictly match the query string. Exact, /// Import map entry should contain all letters from the query string, @@ -277,6 +290,42 @@ enum SearchMode { Prefix, } +impl SearchMode { + pub fn check(self, query: &str, case_sensitive: bool, candidate: &str) -> bool { + match self { + SearchMode::Exact if case_sensitive => candidate == query, + SearchMode::Exact => candidate.eq_ignore_ascii_case(&query), + SearchMode::Prefix => { + query.len() <= candidate.len() && { + let prefix = &candidate[..query.len() as usize]; + if case_sensitive { + prefix == query + } else { + prefix.eq_ignore_ascii_case(&query) + } + } + } + SearchMode::Fuzzy => { + let mut name = candidate; + query.chars().all(|query_char| { + let m = if case_sensitive { + name.match_indices(query_char).next() + } else { + name.match_indices([query_char, query_char.to_ascii_uppercase()]).next() + }; + match m { + Some((index, _)) => { + name = &name[index + 1..]; + true + } + None => false, + } + }) + } + } + } +} + /// Three possible ways to search for the name in associated and/or other items. #[derive(Debug, Clone, Copy)] pub enum AssocSearchMode { @@ -392,67 +441,28 @@ fn search_maps( query: &Query, ) -> FxHashSet { let mut res = FxHashSet::default(); - while let Some((key, indexed_values)) = stream.next() { + while let Some((_, indexed_values)) = stream.next() { for &IndexedValue { index: import_map_idx, value } in indexed_values { - let import_map = &import_maps[import_map_idx]; - let importables = &import_map.importables[value as usize..]; + let end = (value & 0xFFFF_FFFF) as usize; + let start = (value >> 32) as usize; + let ImportMap { item_to_info_map, importables, .. } = &*import_maps[import_map_idx]; + let importables = &importables[start as usize..end]; let iter = importables .iter() .copied() - .map(|(item, info_idx)| { - let (import_infos, assoc_mode) = &import_map.item_to_info_map[&item]; - (item, &import_infos[info_idx as usize], *assoc_mode) + .filter_map(|(item, info_idx)| { + let (import_infos, assoc_mode) = &item_to_info_map[&item]; + query + .matches_assoc_mode(*assoc_mode) + .then(|| (item, &import_infos[info_idx as usize])) }) - // we put all entries with the same lowercased name in a row, so stop once we find a - // different name in the importables - // FIXME: Consider putting a range into the value: u64 as (u32, u32)? - .take_while(|&(_, info, _)| { - info.name.to_smol_str().as_bytes().eq_ignore_ascii_case(&key) - }) - .filter(|&(_, info, assoc_mode)| { - if !query.matches_assoc_mode(assoc_mode) { - return false; - } - if !query.case_sensitive { - return true; - } - let name = info.name.to_smol_str(); - // FIXME: Deduplicate this from ide-db - match query.search_mode { - SearchMode::Exact => !query.case_sensitive || name == query.query, - SearchMode::Prefix => { - query.query.len() <= name.len() && { - let prefix = &name[..query.query.len() as usize]; - if query.case_sensitive { - prefix == query.query - } else { - prefix.eq_ignore_ascii_case(&query.query) - } - } - } - SearchMode::Fuzzy => { - let mut name = &*name; - query.query.chars().all(|query_char| { - let m = if query.case_sensitive { - name.match_indices(query_char).next() - } else { - name.match_indices([ - query_char, - query_char.to_ascii_uppercase(), - ]) - .next() - }; - match m { - Some((index, _)) => { - name = &name[index + 1..]; - true - } - None => false, - } - }) - } - } + .filter(|&(_, info)| { + query.search_mode.check( + &query.query, + query.case_sensitive, + &info.name.to_smol_str(), + ) }); res.extend(iter.map(TupleExt::head)); } diff --git a/crates/hir/src/symbols.rs b/crates/hir/src/symbols.rs index c1e171dacd38..841ddfb9c437 100644 --- a/crates/hir/src/symbols.rs +++ b/crates/hir/src/symbols.rs @@ -18,11 +18,11 @@ use crate::{Module, ModuleDef, Semantics}; /// possible. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct FileSymbol { - // even though name can be derived from the def, we store it for efficiency pub name: SmolStr, pub def: ModuleDef, pub loc: DeclarationLocation, pub container_name: Option, + /// Whether this symbol is a doc alias for the original symbol. pub is_alias: bool, pub is_assoc: bool, } @@ -166,8 +166,6 @@ impl<'a> SymbolCollector<'a> { // FIXME: In case it imports multiple items under different namespaces we just pick one arbitrarily // for now. for id in scope.imports() { - let loc = id.import.lookup(self.db.upcast()); - loc.id.item_tree(self.db.upcast()); let source = id.import.child_source(self.db.upcast()); let Some(use_tree_src) = source.value.get(id.idx) else { continue }; let Some(rename) = use_tree_src.rename() else { continue }; diff --git a/crates/ide-db/src/symbol_index.rs b/crates/ide-db/src/symbol_index.rs index 002b8e7b320f..c2e95ca860ca 100644 --- a/crates/ide-db/src/symbol_index.rs +++ b/crates/ide-db/src/symbol_index.rs @@ -34,7 +34,7 @@ use base_db::{ use fst::{self, raw::IndexedValue, Automaton, Streamer}; use hir::{ db::HirDatabase, - import_map::AssocSearchMode, + import_map::{AssocSearchMode, SearchMode}, symbols::{FileSymbol, SymbolCollector}, Crate, Module, }; @@ -44,22 +44,15 @@ use triomphe::Arc; use crate::RootDatabase; -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -enum SearchMode { - Fuzzy, - Exact, - Prefix, -} - #[derive(Debug, Clone)] pub struct Query { query: String, lowercased: String, - only_types: bool, - libs: bool, mode: SearchMode, assoc_mode: AssocSearchMode, case_sensitive: bool, + only_types: bool, + libs: bool, } impl Query { @@ -381,43 +374,7 @@ impl Query { if non_type_for_type_only_query || !self.matches_assoc_mode(symbol.is_assoc) { continue; } - // FIXME: Deduplicate this from hir-def - let matches = match self.mode { - SearchMode::Exact if self.case_sensitive => symbol.name == self.query, - SearchMode::Exact => symbol.name.eq_ignore_ascii_case(&self.query), - SearchMode::Prefix => { - self.query.len() <= symbol.name.len() && { - let prefix = &symbol.name[..self.query.len() as usize]; - if self.case_sensitive { - prefix == self.query - } else { - prefix.eq_ignore_ascii_case(&self.query) - } - } - } - SearchMode::Fuzzy => { - let mut name = &*symbol.name; - self.query.chars().all(|query_char| { - let m = if self.case_sensitive { - name.match_indices(query_char).next() - } else { - name.match_indices([ - query_char, - query_char.to_ascii_uppercase(), - ]) - .next() - }; - match m { - Some((index, _)) => { - name = &name[index + 1..]; - true - } - None => false, - } - }) - } - }; - if matches { + if self.mode.check(&self.query, self.case_sensitive, &symbol.name) { cb(symbol); } }