Skip to content

internal: Deduplicate some code #16267

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 80 additions & 70 deletions crates/hir-def/src/import_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]> {
Expand Down Expand Up @@ -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,
Expand All @@ -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 {
Expand Down Expand Up @@ -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));
}
Expand Down
4 changes: 1 addition & 3 deletions crates/hir/src/symbols.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
Expand Down Expand Up @@ -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 };
Expand Down
51 changes: 4 additions & 47 deletions crates/ide-db/src/symbol_index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};
Expand All @@ -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 {
Expand Down Expand Up @@ -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);
}
}
Expand Down