Deduplicate some code

This commit is contained in:
Lukas Wirth 2024-01-04 22:53:41 +01:00
parent 2666349392
commit d60638e5e6
3 changed files with 85 additions and 120 deletions

View File

@ -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<ItemInNs> {
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));
}

View File

@ -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<SmolStr>,
/// 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 };

View File

@ -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);
}
}