//! Feature: completion with imports-on-the-fly
//!
//! When completing names in the current scope, proposes additional imports from other modules or crates,
-//! if they can be qualified in the scope and their name contains all symbols from the completion input
-//! (case-insensitive, in any order or places).
+//! if they can be qualified in the scope and their name contains all symbols from the completion input.
+//!
+//! To be considered applicable, the name must contain all input symbols in the given order, not necessarily adjacent.
+//! If any input symbol is not lowercased, the name must contain all symbols in exact case; otherwise the contaning is checked case-insensitively.
//!
//! ```
//! fn main() {
//! ```
//!
//! Also completes associated items, that require trait imports.
+//! If any unresolved and/or partially-qualified path predeces the input, it will be taken into account.
+//! Currently, only the imports with their import path ending with the whole qialifier will be proposed
+//! (no fuzzy matching for qualifier).
+//!
+//! ```
+//! mod foo {
+//! pub mod bar {
+//! pub struct Item;
+//!
+//! impl Item {
+//! pub const TEST_ASSOC: usize = 3;
+//! }
+//! }
+//! }
+//!
+//! fn main() {
+//! bar::Item::TEST_A$0
+//! }
+//! ```
+//! ->
+//! ```
+//! use foo::bar;
+//!
+//! mod foo {
+//! pub mod bar {
+//! pub struct Item;
+//!
+//! impl Item {
+//! pub const TEST_ASSOC: usize = 3;
+//! }
+//! }
+//! }
+//!
+//! fn main() {
+//! bar::Item::TEST_ASSOC
+//! }
+//! ```
+//!
+//! NOTE: currently, if an assoc item comes from a trait that's not currently imported and it also has an unresolved and/or partially-qualified path,
+//! no imports will be proposed.
//!
//! .Fuzzy search details
//!
//! Note that having this flag set to `true` does not guarantee that the feature is enabled: your client needs to have the corredponding
//! capability enabled.
-use hir::{AsAssocItem, ModPath, ScopeDef};
+use hir::ModPath;
use ide_db::helpers::{
import_assets::{ImportAssets, ImportCandidate},
insert_use::ImportScope,
};
-use rustc_hash::FxHashSet;
+use itertools::Itertools;
use syntax::{AstNode, SyntaxNode, T};
-use test_utils::mark;
use crate::{
context::CompletionContext,
if !ctx.config.enable_imports_on_the_fly {
return None;
}
- if ctx.use_item_syntax.is_some()
- || ctx.attribute_under_caret.is_some()
- || ctx.mod_declaration_under_caret.is_some()
- {
+ if ctx.use_item_syntax.is_some() || ctx.is_path_disallowed() {
return None;
}
let potential_import_name = {
let user_input_lowercased = potential_import_name.to_lowercase();
let import_assets = import_assets(ctx, potential_import_name)?;
- let import_scope = ImportScope::find_insert_use_container(
+ let import_scope = ImportScope::find_insert_use_container_with_macros(
position_for_import(ctx, Some(import_assets.import_candidate()))?,
&ctx.sema,
)?;
- let scope_definitions = scope_definitions(ctx);
- let mut all_mod_paths = import_assets
- .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
- .into_iter()
- .map(|(mod_path, item_in_ns)| {
- let scope_item = match item_in_ns {
- hir::ItemInNs::Types(id) => ScopeDef::ModuleDef(id.into()),
- hir::ItemInNs::Values(id) => ScopeDef::ModuleDef(id.into()),
- hir::ItemInNs::Macros(id) => ScopeDef::MacroDef(id.into()),
- };
- (mod_path, scope_item)
- })
- .filter(|(_, proposed_def)| !scope_definitions.contains(proposed_def))
- .collect::<Vec<_>>();
- all_mod_paths.sort_by_cached_key(|(mod_path, _)| {
- compute_fuzzy_completion_order_key(mod_path, &user_input_lowercased)
- });
-
- acc.add_all(all_mod_paths.into_iter().filter_map(|(import_path, definition)| {
- let import_for_trait_assoc_item = match definition {
- ScopeDef::ModuleDef(module_def) => module_def
- .as_assoc_item(ctx.db)
- .and_then(|assoc| assoc.containing_trait(ctx.db))
- .is_some(),
- _ => false,
- };
- let import_edit = ImportEdit {
- import_path,
- import_scope: import_scope.clone(),
- import_for_trait_assoc_item,
- };
- render_resolution_with_import(RenderContext::new(ctx), import_edit, &definition)
- }));
+ acc.add_all(
+ import_assets
+ .search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind)
+ .into_iter()
+ .sorted_by_key(|located_import| {
+ compute_fuzzy_completion_order_key(
+ &located_import.import_path,
+ &user_input_lowercased,
+ )
+ })
+ .filter_map(|import| {
+ render_resolution_with_import(
+ RenderContext::new(ctx),
+ ImportEdit { import, scope: import_scope.clone() },
+ )
+ }),
+ );
Some(())
}
-fn scope_definitions(ctx: &CompletionContext) -> FxHashSet<ScopeDef> {
- let mut scope_definitions = FxHashSet::default();
- ctx.scope.process_all_names(&mut |_, scope_def| {
- scope_definitions.insert(scope_def);
- });
- scope_definitions
-}
-
pub(crate) fn position_for_import<'a>(
ctx: &'a CompletionContext,
import_candidate: Option<&ImportCandidate>,
current_module,
ctx.sema.type_of_expr(dot_receiver)?,
fuzzy_name,
+ dot_receiver.syntax().clone(),
)
} else {
let fuzzy_name_length = fuzzy_name.len();
+ let approximate_node = match current_module.definition_source(ctx.db).value {
+ hir::ModuleSource::SourceFile(s) => s.syntax().clone(),
+ hir::ModuleSource::Module(m) => m.syntax().clone(),
+ hir::ModuleSource::BlockExpr(b) => b.syntax().clone(),
+ };
let assets_for_path = ImportAssets::for_fuzzy_path(
current_module,
ctx.path_qual.clone(),
fuzzy_name,
&ctx.sema,
- );
+ approximate_node,
+ )?;
- if matches!(assets_for_path.as_ref()?.import_candidate(), ImportCandidate::Path(_))
+ if matches!(assets_for_path.import_candidate(), ImportCandidate::Path(_))
&& fuzzy_name_length < 2
{
- mark::hit!(ignore_short_input_for_path);
+ cov_mark::hit!(ignore_short_input_for_path);
None
} else {
- assets_for_path
+ Some(assets_for_path)
}
}
}
proposed_mod_path: &ModPath,
user_input_lowercased: &str,
) -> usize {
- mark::hit!(certain_fuzzy_order_test);
- let proposed_import_name = match proposed_mod_path.segments().last() {
+ cov_mark::hit!(certain_fuzzy_order_test);
+ let import_name = match proposed_mod_path.segments().last() {
Some(name) => name.to_string().to_lowercase(),
None => return usize::MAX,
};
- match proposed_import_name.match_indices(user_input_lowercased).next() {
+ match import_name.match_indices(user_input_lowercased).next() {
Some((first_matching_index, _)) => first_matching_index,
None => usize::MAX,
}
#[cfg(test)]
mod tests {
use expect_test::{expect, Expect};
- use test_utils::mark;
use crate::{
item::CompletionKind,
#[test]
fn short_paths_are_ignored() {
- mark::check!(ignore_short_input_for_path);
+ cov_mark::check!(ignore_short_input_for_path);
check(
r#"
#[test]
fn fuzzy_completions_come_in_specific_order() {
- mark::check!(certain_fuzzy_order_test);
+ cov_mark::check!(certain_fuzzy_order_test);
check(
r#"
//- /lib.rs crate:dep
check(
fixture,
expect![[r#"
- fn weird_function() (dep::test_mod::TestTrait) -> ()
+ fn weird_function() (dep::test_mod::TestTrait) fn()
"#]],
);
check(
fixture,
expect![[r#"
- me random_method() (dep::test_mod::TestTrait) -> ()
+ me random_method() (dep::test_mod::TestTrait) fn(&self)
"#]],
);
}
"#,
expect![[r#"
- me random_method() (dep::test_mod::TestTrait) -> () DEPRECATED
+ me random_method() (dep::test_mod::TestTrait) fn(&self) DEPRECATED
"#]],
);
"#,
expect![[r#"
ct SPECIAL_CONST (dep::test_mod::TestTrait) DEPRECATED
- fn weird_function() (dep::test_mod::TestTrait) -> () DEPRECATED
+ fn weird_function() (dep::test_mod::TestTrait) fn() DEPRECATED
"#]],
);
}
}"#,
);
}
+
+ #[test]
+ fn unresolved_qualifier() {
+ let fixture = r#"
+mod foo {
+ pub mod bar {
+ pub mod baz {
+ pub struct Item;
+ }
+ }
+}
+
+fn main() {
+ bar::baz::Ite$0
+}"#;
+
+ check(
+ fixture,
+ expect![[r#"
+ st foo::bar::baz::Item
+ "#]],
+ );
+
+ check_edit(
+ "Item",
+ fixture,
+ r#"
+ use foo::bar;
+
+ mod foo {
+ pub mod bar {
+ pub mod baz {
+ pub struct Item;
+ }
+ }
+ }
+
+ fn main() {
+ bar::baz::Item
+ }"#,
+ );
+ }
+
+ #[test]
+ fn unresolved_assoc_item_container() {
+ let fixture = r#"
+mod foo {
+ pub struct Item;
+
+ impl Item {
+ pub const TEST_ASSOC: usize = 3;
+ }
+}
+
+fn main() {
+ Item::TEST_A$0
+}"#;
+
+ check(
+ fixture,
+ expect![[r#"
+ ct TEST_ASSOC (foo::Item)
+ "#]],
+ );
+
+ check_edit(
+ "TEST_ASSOC",
+ fixture,
+ r#"
+use foo::Item;
+
+mod foo {
+ pub struct Item;
+
+ impl Item {
+ pub const TEST_ASSOC: usize = 3;
+ }
+}
+
+fn main() {
+ Item::TEST_ASSOC
+}"#,
+ );
+ }
+
+ #[test]
+ fn unresolved_assoc_item_container_with_path() {
+ let fixture = r#"
+mod foo {
+ pub mod bar {
+ pub struct Item;
+
+ impl Item {
+ pub const TEST_ASSOC: usize = 3;
+ }
+ }
+}
+
+fn main() {
+ bar::Item::TEST_A$0
+}"#;
+
+ check(
+ fixture,
+ expect![[r#"
+ ct TEST_ASSOC (foo::bar::Item)
+ "#]],
+ );
+
+ check_edit(
+ "TEST_ASSOC",
+ fixture,
+ r#"
+use foo::bar;
+
+mod foo {
+ pub mod bar {
+ pub struct Item;
+
+ impl Item {
+ pub const TEST_ASSOC: usize = 3;
+ }
+ }
+}
+
+fn main() {
+ bar::Item::TEST_ASSOC
+}"#,
+ );
+ }
+
+ #[test]
+ fn fuzzy_unresolved_path() {
+ check(
+ r#"
+mod foo {
+ pub mod bar {
+ pub struct Item;
+
+ impl Item {
+ pub const TEST_ASSOC: usize = 3;
+ }
+ }
+}
+
+fn main() {
+ bar::ASS$0
+}"#,
+ expect![[]],
+ )
+ }
+
+ #[test]
+ fn unqualified_assoc_items_are_omitted() {
+ check(
+ r#"
+mod something {
+ pub trait BaseTrait {
+ fn test_function() -> i32;
+ }
+
+ pub struct Item1;
+ pub struct Item2;
+
+ impl BaseTrait for Item1 {
+ fn test_function() -> i32 {
+ 1
+ }
+ }
+
+ impl BaseTrait for Item2 {
+ fn test_function() -> i32 {
+ 2
+ }
+ }
+}
+
+fn main() {
+ test_f$0
+}"#,
+ expect![[]],
+ )
+ }
+
+ #[test]
+ fn case_matters() {
+ check(
+ r#"
+mod foo {
+ pub const TEST_CONST: usize = 3;
+ pub fn test_function() -> i32 {
+ 4
+ }
+}
+
+fn main() {
+ TE$0
+}"#,
+ expect![[r#"
+ ct foo::TEST_CONST
+ "#]],
+ );
+
+ check(
+ r#"
+mod foo {
+ pub const TEST_CONST: usize = 3;
+ pub fn test_function() -> i32 {
+ 4
+ }
+}
+
+fn main() {
+ te$0
+}"#,
+ expect![[r#"
+ ct foo::TEST_CONST
+ fn test_function() (foo::test_function) fn() -> i32
+ "#]],
+ );
+
+ check(
+ r#"
+mod foo {
+ pub const TEST_CONST: usize = 3;
+ pub fn test_function() -> i32 {
+ 4
+ }
+}
+
+fn main() {
+ Te$0
+}"#,
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn no_fuzzy_during_fields_of_record_lit_syntax() {
+ check(
+ r#"
+mod m {
+ pub fn some_fn() -> i32 {
+ 42
+ }
+}
+struct Foo {
+ some_field: i32,
+}
+fn main() {
+ let _ = Foo { so$0 };
+}
+"#,
+ expect![[]],
+ );
+ }
+
+ #[test]
+ fn fuzzy_after_fields_of_record_lit_syntax() {
+ check(
+ r#"
+mod m {
+ pub fn some_fn() -> i32 {
+ 42
+ }
+}
+struct Foo {
+ some_field: i32,
+}
+fn main() {
+ let _ = Foo { some_field: so$0 };
+}
+"#,
+ expect![[r#"
+ fn some_fn() (m::some_fn) fn() -> i32
+ "#]],
+ );
+ }
+
+ #[test]
+ fn no_flyimports_in_traits_and_impl_declarations() {
+ check(
+ r#"
+mod m {
+ pub fn some_fn() -> i32 {
+ 42
+ }
+}
+trait Foo {
+ som$0
+}
+"#,
+ expect![[r#""#]],
+ );
+
+ check(
+ r#"
+mod m {
+ pub fn some_fn() -> i32 {
+ 42
+ }
+}
+struct Foo;
+impl Foo {
+ som$0
+}
+"#,
+ expect![[r#""#]],
+ );
+
+ check(
+ r#"
+mod m {
+ pub fn some_fn() -> i32 {
+ 42
+ }
+}
+struct Foo;
+trait Bar {}
+impl Bar for Foo {
+ som$0
+}
+"#,
+ expect![[r#""#]],
+ );
+ }
+
+ #[test]
+ fn no_inherent_candidates_proposed() {
+ check(
+ r#"
+mod baz {
+ pub trait DefDatabase {
+ fn method1(&self);
+ }
+ pub trait HirDatabase: DefDatabase {
+ fn method2(&self);
+ }
+}
+
+mod bar {
+ fn test(db: &dyn crate::baz::HirDatabase) {
+ db.metho$0
+ }
+}
+ "#,
+ expect![[r#""#]],
+ );
+ }
}