//! 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: only the items with import string
-//! containing this whole path will be considered and the corresponding path import will be added:
+//! 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 {
//! 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, ModuleDef, ScopeDef};
+use hir::ModPath;
use ide_db::helpers::{
import_assets::{ImportAssets, ImportCandidate},
insert_use::ImportScope,
};
+use itertools::Itertools;
use syntax::{AstNode, SyntaxNode, T};
use crate::{
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 mut all_imports =
- import_assets.search_for_imports(&ctx.sema, ctx.config.insert_use.prefix_kind);
- all_imports.sort_by_cached_key(|import| {
- compute_fuzzy_completion_order_key(import.display_path(), &user_input_lowercased)
- });
-
- acc.add_all(all_imports.into_iter().filter_map(|import| {
- let import_for_trait_assoc_item = import
- .item_to_display()
- .as_module_def_id()
- .and_then(|module_def_id| {
- ModuleDef::from(module_def_id).as_assoc_item(ctx.db)?.containing_trait(ctx.db)
+ 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,
+ )
})
- .is_some();
- let def_to_display = ScopeDef::from(import.item_to_display());
- render_resolution_with_import(
- RenderContext::new(ctx),
- ImportEdit { import, import_scope: import_scope.clone(), import_for_trait_assoc_item },
- &def_to_display,
- )
- }));
+ .filter_map(|import| {
+ render_resolution_with_import(
+ RenderContext::new(ctx),
+ ImportEdit { import, scope: import_scope.clone() },
+ )
+ }),
+ );
Some(())
}
})
}
-fn import_assets<'a>(ctx: &'a CompletionContext, fuzzy_name: String) -> Option<ImportAssets<'a>> {
+fn import_assets(ctx: &CompletionContext, fuzzy_name: String) -> Option<ImportAssets> {
let current_module = ctx.scope.module()?;
if let Some(dot_receiver) = &ctx.dot_receiver {
ImportAssets::for_fuzzy_method_call(
current_module,
ctx.sema.type_of_expr(dot_receiver)?,
fuzzy_name,
- ctx.scope.clone(),
+ 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,
- ctx.scope.clone(),
+ approximate_node,
)?;
if matches!(assets_for_path.import_candidate(), ImportCandidate::Path(_))
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#"
- fn weird_function() (dep::test_mod::TestTrait) -> () DEPRECATED
ct SPECIAL_CONST (dep::test_mod::TestTrait) DEPRECATED
+ fn weird_function() (dep::test_mod::TestTrait) fn() DEPRECATED
"#]],
);
}
#[test]
fn unresolved_qualifier() {
- check_edit(
- "Item",
- r#"
+ let fixture = r#"
mod foo {
pub mod bar {
pub mod baz {
fn main() {
bar::baz::Ite$0
-}
-"#,
+}"#;
+
+ check(
+ fixture,
+ expect![[r#"
+ st foo::bar::baz::Item
+ "#]],
+ );
+
+ check_edit(
+ "Item",
+ fixture,
r#"
-use foo::bar;
+ use foo::bar;
-mod foo {
- pub mod bar {
- pub mod baz {
- pub struct Item;
+ mod foo {
+ pub mod bar {
+ pub mod baz {
+ pub struct Item;
+ }
+ }
}
- }
-}
-fn main() {
- bar::baz::Item
-}
-"#,
+ fn main() {
+ bar::baz::Item
+ }"#,
);
}
#[test]
fn unresolved_assoc_item_container() {
- check_edit(
- "TEST_ASSOC",
- r#"
+ let fixture = r#"
mod foo {
pub struct Item;
fn main() {
Item::TEST_A$0
-}
-"#,
+}"#;
+
+ check(
+ fixture,
+ expect![[r#"
+ ct TEST_ASSOC (foo::Item)
+ "#]],
+ );
+
+ check_edit(
+ "TEST_ASSOC",
+ fixture,
r#"
use foo::Item;
fn main() {
Item::TEST_ASSOC
-}
-"#,
+}"#,
);
}
#[test]
fn unresolved_assoc_item_container_with_path() {
- check_edit(
- "TEST_ASSOC",
- r#"
+ let fixture = r#"
mod foo {
pub mod bar {
pub struct Item;
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;
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#""#]],
);
}
}