Skip to content
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

Ignore qualifiers when doing autoimport completions lookup #7064

Merged
merged 6 commits into from
Dec 29, 2020
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
11 changes: 8 additions & 3 deletions crates/completion/src/completions/unqualified_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,9 @@ fn complete_enum_variants(acc: &mut Completions, ctx: &CompletionContext, ty: &T
//
// .Fuzzy search details
//
// To avoid an excessive amount of the results returned, completion input is checked for inclusion in the identifiers only
// (i.e. in `HashMap` in the `std::collections::HashMap` path), also not in the module indentifiers.
// To avoid an excessive amount of the results returned, completion input is checked for inclusion in the names only
// (i.e. in `HashMap` in the `std::collections::HashMap` path).
// For the same reasons, avoids searching for any imports for inputs with their length less that 2 symbols.
//
// .Merge Behavior
//
Expand All @@ -126,14 +127,18 @@ fn fuzzy_completion(acc: &mut Completions, ctx: &CompletionContext) -> Option<()
let _p = profile::span("fuzzy_completion");
let potential_import_name = ctx.token.to_string();

if potential_import_name.len() < 2 {
return None;
}

let current_module = ctx.scope.module()?;
let anchor = ctx.name_ref_syntax.as_ref()?;
let import_scope = ImportScope::find_insert_use_container(anchor.syntax(), &ctx.sema)?;

let mut all_mod_paths = imports_locator::find_similar_imports(
&ctx.sema,
ctx.krate?,
Some(100),
Some(40),
&potential_import_name,
true,
)
Expand Down
207 changes: 168 additions & 39 deletions crates/hir_def/src/import_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,11 +238,24 @@ pub enum ImportKind {
BuiltinType,
}

/// A way to match import map contents against the search query.
#[derive(Debug)]
pub enum SearchMode {
/// Import map entry should strictly match the query string.
Equals,
/// Import map entry should contain the query string.
Contains,
/// Import map entry should contain all letters from the query string,
/// in the same order, but not necessary adjacent.
Fuzzy,
}

#[derive(Debug)]
pub struct Query {
query: String,
lowercased: String,
anchor_end: bool,
name_only: bool,
search_mode: SearchMode,
case_sensitive: bool,
limit: usize,
exclude_import_kinds: FxHashSet<ImportKind>,
Expand All @@ -251,19 +264,26 @@ pub struct Query {
impl Query {
pub fn new(query: &str) -> Self {
Self {
lowercased: query.to_lowercase(),
query: query.to_string(),
anchor_end: false,
lowercased: query.to_lowercase(),
name_only: false,
search_mode: SearchMode::Contains,
case_sensitive: false,
limit: usize::max_value(),
exclude_import_kinds: FxHashSet::default(),
}
}

/// Only returns items whose paths end with the (case-insensitive) query string as their last
/// segment.
pub fn anchor_end(self) -> Self {
Self { anchor_end: true, ..self }
/// Matches entries' names only, ignoring the rest of
/// the qualifier.
/// Example: for `std::marker::PhantomData`, the name is `PhantomData`.
pub fn name_only(self) -> Self {
Self { name_only: true, ..self }
}

/// Specifies the way to search for the entries using the query.
pub fn search_mode(self, search_mode: SearchMode) -> Self {
Self { search_mode, ..self }
}

/// Limits the returned number of items to `limit`.
Expand All @@ -283,6 +303,40 @@ impl Query {
}
}

fn contains_query(query: &Query, input_path: &ImportPath, enforce_lowercase: bool) -> bool {
let mut input = if query.name_only {
input_path.segments.last().unwrap().to_string()
} else {
input_path.to_string()
};
if enforce_lowercase || !query.case_sensitive {
input.make_ascii_lowercase();
}

let query_string =
if !enforce_lowercase && query.case_sensitive { &query.query } else { &query.lowercased };

match query.search_mode {
SearchMode::Equals => &input == query_string,
SearchMode::Contains => input.contains(query_string),
SearchMode::Fuzzy => {
let mut unchecked_query_chars = query_string.chars();
let mut mismatching_query_char = unchecked_query_chars.next();

for input_char in input.chars() {
match mismatching_query_char {
None => return true,
Some(matching_query_char) if matching_query_char == input_char => {
mismatching_query_char = unchecked_query_chars.next();
}
_ => (),
}
}
mismatching_query_char.is_none()
}
}
}

/// Searches dependencies of `krate` for an importable path matching `query`.
///
/// This returns a list of items that could be imported from dependencies of `krate`.
Expand Down Expand Up @@ -312,39 +366,29 @@ pub fn search_dependencies<'a>(
let importables = &import_map.importables[indexed_value.value as usize..];

// Path shared by the importable items in this group.
let path = &import_map.map[&importables[0]].path;

if query.anchor_end {
// Last segment must match query.
let last = path.segments.last().unwrap().to_string();
if last.to_lowercase() != query.lowercased {
continue;
}
let common_importables_path = &import_map.map[&importables[0]].path;
if !contains_query(&query, common_importables_path, true) {
continue;
}

let common_importables_path_fst = fst_path(common_importables_path);
// Add the items from this `ModPath` group. Those are all subsequent items in
// `importables` whose paths match `path`.
let iter = importables
.iter()
.copied()
.take_while(|item| {
let item_path = &import_map.map[item].path;
fst_path(item_path) == fst_path(path)
common_importables_path_fst == fst_path(&import_map.map[item].path)
})
.filter(|&item| match item_import_kind(item) {
Some(import_kind) => !query.exclude_import_kinds.contains(&import_kind),
None => true,
})
.filter(|item| {
!query.case_sensitive // we've already checked the common importables path case-insensitively
|| contains_query(&query, &import_map.map[item].path, false)
});

if query.case_sensitive {
// FIXME: This does not do a subsequence match.
res.extend(iter.filter(|item| {
let item_path = &import_map.map[item].path;
item_path.to_string().contains(&query.query)
}));
} else {
res.extend(iter);
}
res.extend(iter);

if res.len() >= query.limit {
res.truncate(query.limit);
Expand Down Expand Up @@ -388,7 +432,7 @@ mod tests {
use base_db::{fixture::WithFixture, SourceDatabase, Upcast};
use expect_test::{expect, Expect};

use crate::{test_db::TestDB, AssocContainerId, Lookup};
use crate::{data::FunctionData, test_db::TestDB, AssocContainerId, Lookup};

use super::*;

Expand All @@ -407,14 +451,31 @@ mod tests {
.into_iter()
.filter_map(|item| {
let mark = match item {
ItemInNs::Types(ModuleDefId::FunctionId(_))
| ItemInNs::Values(ModuleDefId::FunctionId(_)) => "f",
ItemInNs::Types(_) => "t",
ItemInNs::Values(_) => "v",
ItemInNs::Macros(_) => "m",
};
let item = assoc_to_trait(&db, item);
item.krate(db.upcast()).map(|krate| {
let map = db.import_map(krate);
let path = map.path_of(item).unwrap();

let path = match assoc_to_trait(&db, item) {
Some(trait_) => {
let mut full_path = map.path_of(trait_).unwrap().to_string();
if let ItemInNs::Types(ModuleDefId::FunctionId(function_id))
| ItemInNs::Values(ModuleDefId::FunctionId(function_id)) = item
{
full_path += &format!(
lnicola marked this conversation as resolved.
Show resolved Hide resolved
"::{}",
FunctionData::fn_data_query(&db, function_id).name
);
}
full_path
}
None => map.path_of(item).unwrap().to_string(),
};

format!(
"{}::{} ({})\n",
crate_graph[krate].display_name.as_ref().unwrap(),
Expand All @@ -427,15 +488,15 @@ mod tests {
expect.assert_eq(&actual)
}

fn assoc_to_trait(db: &dyn DefDatabase, item: ItemInNs) -> ItemInNs {
fn assoc_to_trait(db: &dyn DefDatabase, item: ItemInNs) -> Option<ItemInNs> {
let assoc: AssocItemId = match item {
ItemInNs::Types(it) | ItemInNs::Values(it) => match it {
ModuleDefId::TypeAliasId(it) => it.into(),
ModuleDefId::FunctionId(it) => it.into(),
ModuleDefId::ConstId(it) => it.into(),
_ => return item,
_ => return None,
},
_ => return item,
_ => return None,
};

let container = match assoc {
Expand All @@ -445,8 +506,8 @@ mod tests {
};

match container {
AssocContainerId::TraitId(it) => ItemInNs::Types(it.into()),
_ => item,
AssocContainerId::TraitId(it) => Some(ItemInNs::Types(it.into())),
_ => None,
}
}

Expand Down Expand Up @@ -685,7 +746,7 @@ mod tests {
}

#[test]
fn search() {
fn search_mode() {
let ra_fixture = r#"
//- /main.rs crate:main deps:dep
//- /dep.rs crate:dep deps:tdep
Expand Down Expand Up @@ -713,28 +774,96 @@ mod tests {
check_search(
ra_fixture,
"main",
Query::new("fmt"),
Query::new("fmt").search_mode(SearchMode::Fuzzy),
expect![[r#"
dep::fmt (t)
dep::Fmt (t)
dep::Fmt (v)
dep::Fmt (m)
dep::fmt::Display (t)
dep::format (v)
dep::format (f)
dep::fmt::Display::fmt (f)
"#]],
);

check_search(
ra_fixture,
"main",
Query::new("fmt").search_mode(SearchMode::Equals),
expect![[r#"
dep::fmt (t)
dep::Fmt (t)
dep::Fmt (v)
dep::Fmt (m)
dep::fmt::Display::fmt (f)
"#]],
);

check_search(
ra_fixture,
"main",
Query::new("fmt").search_mode(SearchMode::Contains),
expect![[r#"
dep::fmt (t)
dep::Fmt (t)
dep::Fmt (v)
dep::Fmt (m)
dep::fmt::Display (t)
dep::fmt::Display::fmt (f)
"#]],
);
}

#[test]
fn name_only() {
let ra_fixture = r#"
//- /main.rs crate:main deps:dep
//- /dep.rs crate:dep deps:tdep
use tdep::fmt as fmt_dep;
pub mod fmt {
pub trait Display {
fn fmt();
}
}
#[macro_export]
macro_rules! Fmt {
() => {};
}
pub struct Fmt;

pub fn format() {}
pub fn no() {}

//- /tdep.rs crate:tdep
pub mod fmt {
pub struct NotImportableFromMain;
}
"#;

check_search(
ra_fixture,
"main",
Query::new("fmt").anchor_end(),
Query::new("fmt"),
expect![[r#"
dep::fmt (t)
dep::Fmt (t)
dep::Fmt (v)
dep::Fmt (m)
dep::fmt::Display (t)
dep::fmt::Display::fmt (f)
"#]],
);

check_search(
ra_fixture,
"main",
Query::new("fmt").name_only(),
expect![[r#"
dep::fmt (t)
dep::Fmt (t)
dep::Fmt (v)
dep::Fmt (m)
dep::fmt::Display::fmt (f)
"#]],
);
}
Expand Down
Loading